Compare commits

...

11 Commits

Author SHA1 Message Date
e2a7f73804
Added link to telegram channel
Fixes #40
2023-09-14 22:23:48 +03:00
fc8f2b527b
Replaced checkbox with text agreements
Fixes #47
2023-09-14 22:18:14 +03:00
a2b0b25233
Fixed unknown category trashboxes
Added specific error for trashboxes token expiration
2023-09-14 01:30:51 +03:00
1f7f69e933
Changed poems adding to packet add_all 2023-09-14 01:30:08 +03:00
860ea43091
Added rounded corners for poem pic 2023-09-14 01:29:20 +03:00
32d1b1b0e6
Added tipical student locations 2023-09-14 00:34:37 +03:00
7ecfe6faa4 Merge pull request 'asynchronous-porridger' (#49) from asynchronous-porridger into main
Reviewed-on: dm1sh/porridger#49
2023-09-13 22:46:16 +03:00
7a4b3978a7
Fixed stories preview buttons color 2023-09-13 22:43:17 +03:00
f2de7c419e
Humanized form captions
Related to #42
2023-09-05 08:42:19 +03:00
e9bf7eabaf
Made 'Петроградская' default metro station
Related to #44
2023-09-05 08:35:45 +03:00
74f89ae7cb
Fixed poetry image width 2023-09-05 08:26:35 +03:00
21 changed files with 197 additions and 41 deletions

1
.gitignore vendored
View File

@ -28,6 +28,7 @@ dist-ssr
*.db *.db
uploads/ uploads/
.env .env
poem_pic/
poem_pic/ poem_pic/

View File

@ -11,6 +11,7 @@ from . import auth_utils, orm_models, pydantic_schemas
# Загружаем стихи # Загружаем стихи
async def add_poems_to_db(async_db: AsyncSession): async def add_poems_to_db(async_db: AsyncSession):
poems = []
f1 = open('poems.txt', encoding='utf-8', mode='r')#открыть фаил для чтения на русском f1 = open('poems.txt', encoding='utf-8', mode='r')#открыть фаил для чтения на русском
for a in range(1, 110): for a in range(1, 110):
f1.seek(0)#перейти к началу f1.seek(0)#перейти к началу
@ -35,8 +36,11 @@ async def add_poems_to_db(async_db: AsyncSession):
author += str1 author += str1
poem = orm_models.Poems(title=name, text=stixi, author=author) poem = orm_models.Poems(title=name, text=stixi, author=author)
# В конце каждой итерации добавляем в базу данных # В конце каждой итерации добавляем в базу данных
async_db.add(poem) poems.append(poem)
async_db.commit()
async_db.add_all(poems)
await async_db.commit()
# close the file # close the file
f1.close() f1.close()

View File

@ -267,12 +267,18 @@ async def get_trashboxes(data: pydantic_schemas.TrashboxRequest = Depends()): #
'limit' : '1' 'limit' : '1'
} }
# Перевод категории с фронта на категорию с сайта # Перевод категории с фронта на категорию с сайта
list_of_category = trashboxes_category[data.Category] try:
list_of_category = trashboxes_category[data.Category]
except:
list_of_category = trashboxes_category['other_things']
# Получение ответа от стороннего апи # Получение ответа от стороннего апи
response = requests.post(TRASHBOXES_BASE_URL + "/nearest_recycling/get", headers=head, data=my_data, timeout=10) response = requests.post(TRASHBOXES_BASE_URL + "/nearest_recycling/get", headers=head, data=my_data, timeout=10)
infos = response.json() infos = response.json()
if 'error' in infos and infos['error_description'] == 'Invalid bearer token':
raise HTTPException(status_code=502, detail="Invalid trashboxes token")
# Чтение ответа # Чтение ответа
trashboxes = [] trashboxes = []
for trashbox in infos["results"]: for trashbox in infos["results"]:

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -105,5 +105,7 @@ const lineByName = (name: string) => (
lines.find(line => stations[line].has(name)) lines.find(line => stations[line].has(name))
) )
const DEFAULT_LINE = 'Петроградская'
export type { Lines } export type { Lines }
export { lines, stations, colors, lineNames, lineByName } export { lines, stations, colors, lineNames, lineByName, DEFAULT_LINE }

View File

@ -0,0 +1,73 @@
import { iconDormitory, iconITMO, iconLETI } from '../utils/markerIcons'
type LocationType = 'dormitory' | 'leti' | 'itmo'
const studentLocations: {
name: string,
position: [number, number],
type: LocationType
}[] = [
{
name: 'Первое, второе, третье общежития',
position: [59.987299, 30.330672],
type: 'dormitory',
},
{
name: 'Четвертое общежитие',
position: [59.985620, 30.331319],
type: 'dormitory',
},
{
name: 'Шестое общежитие',
position: [59.969713, 30.299851],
type: 'dormitory',
},
{
name: 'Седьмое общежитие',
position: [60.003723, 30.287616],
type: 'dormitory',
},
{
name: 'Восьмое общежитие',
position: [59.991115, 30.318752],
type: 'dormitory',
},
{
name: 'Общежития Межвузовского студенческого городка',
position: [59.871053, 30.307154],
type: 'dormitory',
},
{
name: 'Одиннадцатое общежитие',
position: [59.877962, 30.242889],
type: 'dormitory',
},
{
name: 'Общежитие Академии транспортных технологий',
position: [59.870375, 30.308646],
type: 'dormitory',
},
{
name: 'ЛЭТИ шестой корпус',
position: [59.971578, 30.296653],
type: 'leti',
},
{
name: 'ЛЭТИ Первый и другие корпуса',
position: [59.971947, 30.324303],
type: 'leti',
},
{
name: 'ИТМО',
position: [59.956363, 30.310029],
type: 'itmo',
},
]
const locationsIcons: Record<LocationType, L.Icon> = {
dormitory: iconDormitory,
itmo: iconITMO,
leti: iconLETI,
}
export { studentLocations, locationsIcons }

View File

@ -11,6 +11,7 @@ import { iconItem } from '../utils/markerIcons'
import { useId } from '../hooks' import { useId } from '../hooks'
import SelectDisposalTrashbox from './SelectDisposalTrashbox' import SelectDisposalTrashbox from './SelectDisposalTrashbox'
import StarRating from './StarRating' import StarRating from './StarRating'
import StudentLocations from './StudentLocations'
const styles = { const styles = {
container: { container: {
@ -52,6 +53,8 @@ const View = ({
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/> />
<StudentLocations />
<Marker icon={iconItem} position={[lat, lng]}> <Marker icon={iconItem} position={[lat, lng]}>
<Popup> <Popup>
{address} {address}

View File

@ -47,18 +47,13 @@ const AuthForm = ({ goBack }: AuthFormProps) => {
<Form.Control placeholder='Пароль' name='password' type='password' required /> <Form.Control placeholder='Пароль' name='password' type='password' required />
</Form.Group> </Form.Group>
<Form.Group className='mb-3' controlId='privacyPolicyConsent'> <p>
<Form.Check> Нажимая на кнопку, я даю своё согласие на обработку персональных данных и соглашаюсь с{' '}
<Form.Check.Input type='checkbox' required /> <a
<Form.Check.Label> href={`${document.location.origin}/privacy_policy.pdf`}
Я согласен с{' '} target='_blank'
<a >условиями политики конфиденциальности</a>
href={`${document.location.origin}/privacy_policy.pdf`} </p>
target='_blank'
>условиями обработки персональных данных</a>
</Form.Check.Label>
</Form.Check>
</Form.Group>
<ButtonGroup className='d-flex'> <ButtonGroup className='d-flex'>
<Button <Button

View File

@ -77,7 +77,7 @@ function Filters({ filter, setFilter, filterShown, setFilterShown }: FiltersProp
</Form.Group> </Form.Group>
<Button variant='success' type='submit'> <Button variant='success' type='submit'>
Отправить Выбрать
</Button> </Button>
</Form> </Form>
</Modal.Body> </Modal.Body>

View File

@ -24,9 +24,10 @@ function LocationMarker({ address, position, setPosition }: LocationMarkerProps)
}) })
return ( return (
<Marker icon={iconItem} position={position}> <Marker icon={iconItem} position={position} zIndexOffset={1000}>
<Popup> <Popup>
{address} {address}
<br />
{position.lat.toFixed(4)}, {position.lng.toFixed(4)} {position.lat.toFixed(4)}, {position.lng.toFixed(4)}
</Popup> </Popup>
</Marker> </Marker>

View File

@ -26,7 +26,7 @@ function Poetry() {
}} }}
/> />
<p><em>{poetry.data.author}</em></p> <p><em>{poetry.data.author}</em></p>
<img src={`/poem_pic/${poetry.data.id}.jpg`} alt='Иллюстрация' /> <img className={styles.image} src={`/poem_pic/${poetry.data.id}.jpg`} alt='Иллюстрация' />
</> </>
) )
) : ( ) : (

View File

@ -1,6 +1,5 @@
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { useEffect, useLayoutEffect, useRef, useState } from 'react' import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { Button } from 'react-bootstrap'
import { UserCategory, composeUserCategoriesFilters, userCategoriesInfos } from '../assets/userCategories' import { UserCategory, composeUserCategoriesFilters, userCategoriesInfos } from '../assets/userCategories'
import { Announcement } from '../api/announcement/types' import { Announcement } from '../api/announcement/types'
@ -96,9 +95,9 @@ function StoriesPreviewCarousel({ announcements, category }: StoriesPreviewProps
return <div className={styles.container}> return <div className={styles.container}>
{showScrollButtons.left && {showScrollButtons.left &&
<Button onClick={doScroll(false)} className={`${styles.scrollButton} ${styles.leftScrollButton}`}> <button onClick={doScroll(false)} className={`${styles.scrollButton} ${styles.leftScrollButton}`}>
<img src={rightAngleIcon} alt='Показать ещё' /> <img src={rightAngleIcon} alt='Показать ещё' />
</Button> </button>
} }
<ul className={styles.list} ref={ulElement}> <ul className={styles.list} ref={ulElement}>
@ -106,9 +105,9 @@ function StoriesPreviewCarousel({ announcements, category }: StoriesPreviewProps
</ul> </ul>
{showScrollButtons.right && {showScrollButtons.right &&
<Button onClick={doScroll(true)} className={`${styles.scrollButton} ${styles.rightScrollButton}`}> <button onClick={doScroll(true)} className={`${styles.scrollButton} ${styles.rightScrollButton}`}>
<img src={rightAngleIcon} alt='Показать ещё' /> <img src={rightAngleIcon} alt='Показать ещё' />
</Button> </button>
} }
</div> </div>
} }

View File

@ -0,0 +1,36 @@
import { Marker, Tooltip, useMap } from 'react-leaflet'
import { LatLng } from 'leaflet'
import { locationsIcons, studentLocations } from '../assets/studentLocations'
type StudentLocationsProps = {
setPosition?: (pos: LatLng) => void
}
function StudentLocations({ setPosition }: StudentLocationsProps) {
const map = useMap()
return (
<>{
studentLocations.map((el) =>
<Marker
icon={locationsIcons[el.type]}
position={el.position}
eventHandlers={{
click: setPosition && (() => {
setPosition(new LatLng(...el.position))
map.setView(el.position)
}),
}}
>
<Tooltip>
{el.name}
</Tooltip>
</Marker>
)
}
</>
)
}
export default StudentLocations

View File

@ -17,3 +17,4 @@ export { default as SelectDisposalTrashbox } from './SelectDisposalTrashbox'
export { default as StarRating } from './StarRating' export { default as StarRating } from './StarRating'
export { default as CardLayout } from './CardLayout' export { default as CardLayout } from './CardLayout'
export { default as LocateButton } from './LocateButton' export { default as LocateButton } from './LocateButton'
export { default as StudentLocations } from './StudentLocations'

View File

@ -1,13 +1,13 @@
import { CSSProperties, FormEventHandler, useEffect, useState } from 'react' import { CSSProperties, FormEventHandler, useEffect, useMemo, useState } from 'react'
import { Form, Button } from 'react-bootstrap' import { Form, Button } from 'react-bootstrap'
import { MapContainer, TileLayer } from 'react-leaflet' import { MapContainer, TileLayer } from 'react-leaflet'
import { latLng } from 'leaflet' import { latLng } from 'leaflet'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { MapClickHandler, LocationMarker, CardLayout, LocateButton } from '../components' import { MapClickHandler, LocationMarker, CardLayout, LocateButton, StudentLocations } from '../components'
import { useAddAnnouncement } from '../hooks/api' import { useAddAnnouncement } from '../hooks/api'
import { categories, categoryNames } from '../assets/category' import { categories, categoryNames } from '../assets/category'
import { stations, lines, lineNames } from '../assets/metro' import { stations, lines, lineNames, DEFAULT_LINE } from '../assets/metro'
import { fallbackError, gotResponse } from '../hooks/useFetch' import { fallbackError, gotResponse } from '../hooks/useFetch'
import { useOsmAddresses } from '../hooks/api' import { useOsmAddresses } from '../hooks/api'
@ -38,7 +38,7 @@ function AddPage() {
formData.append('address', address.data || '') // if address.error formData.append('address', address.data || '') // if address.error
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())
handleAdd(formData) void handleAdd(formData)
} }
useEffect(() => { useEffect(() => {
@ -51,15 +51,15 @@ function AddPage() {
<CardLayout text='Опубликовать объявление'> <CardLayout text='Опубликовать объявление'>
<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 =>
<option key={category} value={category}>{categoryNames[category]}</option> <option key={category} value={category}>{categoryNames[category]}</option>
@ -68,12 +68,12 @@ function AddPage() {
</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}
@ -86,6 +86,8 @@ function AddPage() {
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/> />
{useMemo(() => <StudentLocations setPosition={setAddressPosition} />, [])}
<LocationMarker <LocationMarker
address={gotResponse(address) ? fallbackError(address) : 'Загрузка...'} address={gotResponse(address) ? fallbackError(address) : 'Загрузка...'}
position={addressPosition} position={addressPosition}
@ -103,7 +105,7 @@ function AddPage() {
</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 <Form.Control
as='textarea' as='textarea'
name='description' name='description'
@ -113,7 +115,7 @@ function AddPage() {
</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'
@ -124,12 +126,9 @@ function AddPage() {
<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' defaultValue={DEFAULT_LINE}>
<option value=''>
Укажите ближайщую станцию метро
</option>
{lines.map( {lines.map(
line => line =>
<optgroup key={line} label={lineNames[line]}> <optgroup key={line} label={lineNames[line]}>

View File

@ -48,6 +48,11 @@ function UserPage() {
<div className='mb-3'> <div className='mb-3'>
<Poetry /> <Poetry />
</div> </div>
<div className='mb-3'>
<h4>Связаться с разработчиками</h4>
Канал в телеграме: <a href='https://t.me/porridger'>Porridger</a>
</div>
</Container> </Container>
) )
} }

View File

@ -4,4 +4,9 @@
.text { .text {
white-space: pre-wrap; white-space: pre-wrap;
}
.image {
width: 100%;
border-radius: 12px;
} }

View File

@ -42,6 +42,7 @@
top: 0; top: 0;
z-index: 100; z-index: 100;
background: linear-gradient(to right, rgba(17, 17, 17, 0) 0%, rgba(17, 17, 17, 255) 100%); background: linear-gradient(to right, rgba(17, 17, 17, 0) 0%, rgba(17, 17, 17, 255) 100%);
background-color: transparent;
display: block; display: block;
height: 100%; height: 100%;
width: 10%; width: 10%;

View File

@ -2,6 +2,10 @@ import L from 'leaflet'
import itemMarker from '../assets/itemMarker.png' import itemMarker from '../assets/itemMarker.png'
import trashMarker from '../assets/trashMarker.png' import trashMarker from '../assets/trashMarker.png'
import dormitoryMarker from '../assets/dormitoryMarker.png'
import letiMarker from '../assets/letiMarker.png'
import itmoMarker from '../assets/itmoMarker.png'
const iconItem = new L.Icon({ const iconItem = new L.Icon({
iconUrl: itemMarker, iconUrl: itemMarker,
@ -17,4 +21,25 @@ const iconTrash = new L.Icon({
iconSize: [34, 41], iconSize: [34, 41],
}) })
export { iconItem, iconTrash } const iconDormitory = new L.Icon({
iconUrl: dormitoryMarker,
iconRetinaUrl: dormitoryMarker,
popupAnchor: [0, 0],
iconSize: [41, 41],
})
const iconLETI = new L.Icon({
iconUrl: letiMarker,
iconRetinaUrl: letiMarker,
popupAnchor: [0, 0],
iconSize: [41, 41],
})
const iconITMO = new L.Icon({
iconUrl: itmoMarker,
iconRetinaUrl: itmoMarker,
popupAnchor: [0, 0],
iconSize: [41, 41],
})
export { iconItem, iconTrash, iconDormitory, iconLETI, iconITMO }