From 79c09087370bec7bf659dd9512bef0eead3bdfbd Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Tue, 30 Jan 2024 16:34:18 +0300 Subject: [PATCH 01/22] added Yandex map with geocoder --- package-lock.json | 22 ++++++ package.json | 1 + src/components/elements/FindFood/FindFood.tsx | 2 + src/components/elements/FindFood/Maps.js | 74 +++++++++++++++++++ .../elements/FindFood/YandexGeocoder.js | 11 +++ src/components/elements/FindFood/balloon.css | 28 +++++++ .../elements/FindFood/balloon.module.scss | 16 ++++ src/utils/getCurrentPosition.js | 10 +++ src/utils/getGeolocationCoordinates.js | 3 + 9 files changed, 167 insertions(+) create mode 100644 src/components/elements/FindFood/Maps.js create mode 100644 src/components/elements/FindFood/YandexGeocoder.js create mode 100644 src/components/elements/FindFood/balloon.css create mode 100644 src/components/elements/FindFood/balloon.module.scss create mode 100644 src/utils/getCurrentPosition.js create mode 100644 src/utils/getGeolocationCoordinates.js diff --git a/package-lock.json b/package-lock.json index ca8e237..d46291b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@hookform/resolvers": "^3.3.2", + "@pbe/react-yandex-maps": "^1.2.5", "@reduxjs/toolkit": "^1.9.3", "@types/lodash.debounce": "^4.0.9", "@types/react": "^18.2.43", @@ -3908,6 +3909,21 @@ "node": ">= 8" } }, + "node_modules/@pbe/react-yandex-maps": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@pbe/react-yandex-maps/-/react-yandex-maps-1.2.5.tgz", + "integrity": "sha512-cBojin5e1fPx9XVCAqHQJsCnHGMeBNsP0TrNfpWCrPFfxb30ye+JgcGr2mn767Gbr1d+RufBLRiUcX2kaiAwjQ==", + "dev": true, + "dependencies": { + "@types/yandex-maps": "2.1.29" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x" + } + }, "node_modules/@pkgr/utils": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", @@ -4837,6 +4853,12 @@ "@types/node": "*" } }, + "node_modules/@types/yandex-maps": { + "version": "2.1.29", + "resolved": "https://registry.npmjs.org/@types/yandex-maps/-/yandex-maps-2.1.29.tgz", + "integrity": "sha512-nuibRWj3RU/9KXlCzTrRtDE+n6V9l7NbT9JakicqZ5OXIdwyb6blvV2Uwn6lB58WYm3DSUDP2I2AWlnWMc8z2w==", + "dev": true + }, "node_modules/@types/yargs": { "version": "16.0.9", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", diff --git a/package.json b/package.json index 5ff5ed1..a82c593 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@hookform/resolvers": "^3.3.2", + "@pbe/react-yandex-maps": "^1.2.5", "@reduxjs/toolkit": "^1.9.3", "@types/lodash.debounce": "^4.0.9", "@types/react": "^18.2.43", diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index edb9c9c..38d1bce 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -5,6 +5,7 @@ import { FC, useRef, useState } from 'react'; import { TextInput } from '../../ui/TextInput'; import { SearchButton } from '../../ui/buttons/SearchButton'; import { DeliveryMethod } from './DeliveryMethod'; +import { Maps } from './Maps'; import style from './findFood.module.scss'; export const FindFood: FC = () => { @@ -31,6 +32,7 @@ export const FindFood: FC = () => { + diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js new file mode 100644 index 0000000..31b6b29 --- /dev/null +++ b/src/components/elements/FindFood/Maps.js @@ -0,0 +1,74 @@ +import { Map, ObjectManager, Placemark, YMaps } from '@pbe/react-yandex-maps'; +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; + +import { getGeolocationCoordinates } from '../../../utils/getGeolocationCoordinates'; +import { YandexGeocoder } from './YandexGeocoder'; +import './balloon.css'; + +const getBalloon = (address) => `
+
Your location
+
${address}
+
`; + +export const Maps = () => { + const { placemarks } = useSelector((state) => state.restaurants); + const [geolocation, setGeolocation] = useState(null); + const address = 'Дубай, бульвар Мухаммед Бин Рашид, дом 1'; + const yandexGeocoder = new YandexGeocoder(); + + useEffect(() => { + yandexGeocoder.adressToGeopoint(address).then((res) => { + const [lon, lat] = getGeolocationCoordinates(res); + setGeolocation([lat, lon]); + }); + }, [address]); + + if (geolocation) { + return ( + + + + + + + ); + } else { + return null; + } +}; diff --git a/src/components/elements/FindFood/YandexGeocoder.js b/src/components/elements/FindFood/YandexGeocoder.js new file mode 100644 index 0000000..3148359 --- /dev/null +++ b/src/components/elements/FindFood/YandexGeocoder.js @@ -0,0 +1,11 @@ +export class YandexGeocoder { + constructor() { + this.api_key = process.env.REACT_APP_YANDEX_API_KEY; + this.geocoder_url = 'https://geocode-maps.yandex.ru/1.x'; + } + async adressToGeopoint(address) { + const response = await fetch(`${this.geocoder_url}?apikey=${this.api_key}&geocode=${address}&format=json`); + const result = await response.json(); + return result; + } +} diff --git a/src/components/elements/FindFood/balloon.css b/src/components/elements/FindFood/balloon.css new file mode 100644 index 0000000..173d56c --- /dev/null +++ b/src/components/elements/FindFood/balloon.css @@ -0,0 +1,28 @@ +.balloon {} + +.balloon__link {} + +.balloon__logo { + width: 150px; + height: auto; +} + +.balloon__image { + max-width: 100%; + height: auto; +} + +.balloon__rest {} + +.balloon__contact {} + +.balloon__contact-link {} + +.balloon__address {} + +.map { + max-width: 856px; + min-height: 390px; + border-radius: 15px; + padding: 10px 0; +} \ No newline at end of file diff --git a/src/components/elements/FindFood/balloon.module.scss b/src/components/elements/FindFood/balloon.module.scss new file mode 100644 index 0000000..e88fcae --- /dev/null +++ b/src/components/elements/FindFood/balloon.module.scss @@ -0,0 +1,16 @@ +.balloon__logo { + width: 150px; + height: auto; +} + +.balloon__image { + max-width: 100%; + height: auto; +} + +.map { + max-width: 856px; + height: 390px; + border-radius: 15px; + padding: 10px 0; +} \ No newline at end of file diff --git a/src/utils/getCurrentPosition.js b/src/utils/getCurrentPosition.js new file mode 100644 index 0000000..719faee --- /dev/null +++ b/src/utils/getCurrentPosition.js @@ -0,0 +1,10 @@ +export const getCoords = async () => { + const pos = await new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition(resolve, reject); + }); + + return { + lat: pos.coords.latitude, + long: pos.coords.longitude, + }; +}; diff --git a/src/utils/getGeolocationCoordinates.js b/src/utils/getGeolocationCoordinates.js new file mode 100644 index 0000000..4c1d56d --- /dev/null +++ b/src/utils/getGeolocationCoordinates.js @@ -0,0 +1,3 @@ +export const getGeolocationCoordinates = (response) => { + return response?.response?.GeoObjectCollection?.featureMember[0]['GeoObject']['Point']['pos'].split(' '); +}; From 2b29e021d2a13be6008f26b07875dc1cac7972c3 Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Tue, 30 Jan 2024 17:57:20 +0300 Subject: [PATCH 02/22] updated Maps --- src/components/elements/FindFood/FindFood.tsx | 24 ++++- src/components/elements/FindFood/Maps.js | 99 ++++++++----------- .../elements/FindFood/YandexGeocoder.js | 3 +- 3 files changed, 64 insertions(+), 62 deletions(-) diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 38d1bce..fb8c412 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -1,21 +1,39 @@ import { faLocationDot } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { FC, useRef, useState } from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; +import { getGeolocationCoordinates } from '../../../utils/getGeolocationCoordinates'; import { TextInput } from '../../ui/TextInput'; import { SearchButton } from '../../ui/buttons/SearchButton'; import { DeliveryMethod } from './DeliveryMethod'; import { Maps } from './Maps'; +import { YandexGeocoder } from './YandexGeocoder'; import style from './findFood.module.scss'; export const FindFood: FC = () => { const searchRef = useRef(null); + const [searchValue, setSearchValue] = useState(''); + const [address, setAddress] = useState(''); + const [geolocation, setGeolocation] = useState([59.94971367493227, 30.35151817345885]); + + const handleSearch = () => { + setAddress(searchValue); + }; const handleSearchValue = (text: string) => { setSearchValue(text); }; + // const address = 'Дубай, бульвар Мухаммед Бин Рашид, дом 1'; + const yandexGeocoder = new YandexGeocoder(); + useEffect(() => { + if (address) + yandexGeocoder.getAddressToGeopoint(address).then((res) => { + const [lon, lat] = getGeolocationCoordinates(res); + setGeolocation([lat, lon]); + }); + }, [address]); return (
@@ -30,9 +48,9 @@ export const FindFood: FC = () => { - +
- + diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 31b6b29..de4ba6c 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -1,9 +1,6 @@ import { Map, ObjectManager, Placemark, YMaps } from '@pbe/react-yandex-maps'; -import { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { getGeolocationCoordinates } from '../../../utils/getGeolocationCoordinates'; -import { YandexGeocoder } from './YandexGeocoder'; import './balloon.css'; const getBalloon = (address) => `
@@ -11,64 +8,50 @@ const getBalloon = (address) => `
${address}
`; -export const Maps = () => { +export const Maps = ({ address, geolocation }) => { const { placemarks } = useSelector((state) => state.restaurants); - const [geolocation, setGeolocation] = useState(null); - const address = 'Дубай, бульвар Мухаммед Бин Рашид, дом 1'; - const yandexGeocoder = new YandexGeocoder(); - useEffect(() => { - yandexGeocoder.adressToGeopoint(address).then((res) => { - const [lon, lat] = getGeolocationCoordinates(res); - setGeolocation([lat, lon]); - }); - }, [address]); - - if (geolocation) { - return ( - + - + - - - - - ); - } else { - return null; - } + features={placemarks} + modules={['objectManager.addon.objectsBalloon', 'objectManager.addon.objectsHint']} + /> + + + ); }; diff --git a/src/components/elements/FindFood/YandexGeocoder.js b/src/components/elements/FindFood/YandexGeocoder.js index 3148359..cff1469 100644 --- a/src/components/elements/FindFood/YandexGeocoder.js +++ b/src/components/elements/FindFood/YandexGeocoder.js @@ -3,7 +3,8 @@ export class YandexGeocoder { this.api_key = process.env.REACT_APP_YANDEX_API_KEY; this.geocoder_url = 'https://geocode-maps.yandex.ru/1.x'; } - async adressToGeopoint(address) { + + async getAddressToGeopoint(address) { const response = await fetch(`${this.geocoder_url}?apikey=${this.api_key}&geocode=${address}&format=json`); const result = await response.json(); return result; From d254b0a278988a4b9869607ca8dbfd750d7bdef9 Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Thu, 1 Feb 2024 18:15:27 +0300 Subject: [PATCH 03/22] added delivery zone to map --- .../FeaturedRestaurants.tsx | 2 +- src/components/elements/FindFood/FindFood.tsx | 54 +++++++++++++++---- src/components/elements/FindFood/Maps.js | 36 +++++++++---- .../elements/FindFood/YandexGeocoder.js | 15 ++++-- .../elements/FindFood/findFood.module.scss | 2 +- .../elements/FindFood/getDeliveryZone.js | 27 ++++++++++ .../elements/Header/DeliverAddress.tsx | 8 +-- src/components/elements/Header/Header.tsx | 7 ++- src/components/elements/Header/MobileMenu.tsx | 2 +- .../pages/SearchPage/SearchPanel.tsx | 3 +- src/components/ui/TextInput/TextInput.tsx | 7 +-- src/store/slices/restaurants/slice.ts | 45 +++++++++++++++- src/store/slices/restaurants/types.ts | 22 ++++++++ src/utils/getAddress.js | 5 ++ src/utils/getBalloon.js | 33 ++++++++++++ 15 files changed, 229 insertions(+), 39 deletions(-) create mode 100644 src/components/elements/FindFood/getDeliveryZone.js create mode 100644 src/utils/getAddress.js create mode 100644 src/utils/getBalloon.js diff --git a/src/components/elements/FeaturedRestaurants/FeaturedRestaurants.tsx b/src/components/elements/FeaturedRestaurants/FeaturedRestaurants.tsx index 7fc7727..eba1010 100644 --- a/src/components/elements/FeaturedRestaurants/FeaturedRestaurants.tsx +++ b/src/components/elements/FeaturedRestaurants/FeaturedRestaurants.tsx @@ -53,7 +53,7 @@ export const FeaturedRestaurants: FC = () => { dispatch( fetchRestaurants({ category: categoryNames[category], - limit, + limit: 20, orderType, sortType, }), diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index fb8c412..0a9615b 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -1,7 +1,18 @@ import { faLocationDot } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FC, useEffect, useRef, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useAppDispatch } from '../../../store'; +import { + addressSelector, + coordsSelector, + placemarkSelector, + restaurantListSelector, + setLocation, + setPlacemarks, +} from '../../../store/slices/restaurants/slice'; +import { getExactAddress } from '../../../utils/getAddress'; import { getGeolocationCoordinates } from '../../../utils/getGeolocationCoordinates'; import { TextInput } from '../../ui/TextInput'; import { SearchButton } from '../../ui/buttons/SearchButton'; @@ -12,28 +23,45 @@ import style from './findFood.module.scss'; export const FindFood: FC = () => { const searchRef = useRef(null); + const dispatch = useAppDispatch(); const [searchValue, setSearchValue] = useState(''); - const [address, setAddress] = useState(''); - const [geolocation, setGeolocation] = useState([59.94971367493227, 30.35151817345885]); + const [requestText, setRequestText] = useState(''); + + const list = useSelector(restaurantListSelector); + const placemarks = useSelector(placemarkSelector); + const coords = useSelector(coordsSelector); + const address = useSelector(addressSelector); + + useEffect(() => { + if (list.length) { + dispatch(setPlacemarks()); + } + }, [list]); const handleSearch = () => { - setAddress(searchValue); + setRequestText(searchValue); }; const handleSearchValue = (text: string) => { setSearchValue(text); }; - // const address = 'Дубай, бульвар Мухаммед Бин Рашид, дом 1'; + const yandexGeocoder = new YandexGeocoder(); useEffect(() => { - if (address) - yandexGeocoder.getAddressToGeopoint(address).then((res) => { + if (requestText) + yandexGeocoder.getAddressAndGeopoint(requestText).then((res) => { const [lon, lat] = getGeolocationCoordinates(res); - setGeolocation([lat, lon]); + const address = getExactAddress(res); + + if (address) { + setRequestText(address); + dispatch(setLocation({ address, coords: [lat, lon] })); + } }); - }, [address]); + }, [requestText]); + return (
@@ -45,12 +73,18 @@ export const FindFood: FC = () => {
- +
- + + {/* */}
diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index de4ba6c..13c3adc 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -1,32 +1,48 @@ -import { Map, ObjectManager, Placemark, YMaps } from '@pbe/react-yandex-maps'; -import { useSelector } from 'react-redux'; +/* eslint-disable max-len */ +import { GeolocationControl, Map, ObjectManager, Placemark, Polygon, YMaps } from '@pbe/react-yandex-maps'; import './balloon.css'; +import { reverseСoordinates } from './getDeliveryZone'; -const getBalloon = (address) => `
+const getMiniBalloon = (address) => `
Your location
${address}
`; -export const Maps = ({ address, geolocation }) => { - const { placemarks } = useSelector((state) => state.restaurants); - +export const Maps = ({ address, geolocation, placemarks }) => { return ( + + {/* */} { iconLayout: 'default#image', }} properties={{ - balloonContent: getBalloon(address), + balloonContent: getMiniBalloon(address), }} geometry={geolocation} /> diff --git a/src/components/elements/FindFood/YandexGeocoder.js b/src/components/elements/FindFood/YandexGeocoder.js index cff1469..7ac5f08 100644 --- a/src/components/elements/FindFood/YandexGeocoder.js +++ b/src/components/elements/FindFood/YandexGeocoder.js @@ -3,10 +3,15 @@ export class YandexGeocoder { this.api_key = process.env.REACT_APP_YANDEX_API_KEY; this.geocoder_url = 'https://geocode-maps.yandex.ru/1.x'; } - - async getAddressToGeopoint(address) { - const response = await fetch(`${this.geocoder_url}?apikey=${this.api_key}&geocode=${address}&format=json`); - const result = await response.json(); - return result; + + async getAddressAndGeopoint(address) { + try { + const response = await fetch(`${this.geocoder_url}?apikey=${this.api_key}&geocode=${address}&format=json`); + + const result = await response.json(); + return result; + } catch (e) { + console.log(e); + } } } diff --git a/src/components/elements/FindFood/findFood.module.scss b/src/components/elements/FindFood/findFood.module.scss index 25e4c07..1790dd3 100644 --- a/src/components/elements/FindFood/findFood.module.scss +++ b/src/components/elements/FindFood/findFood.module.scss @@ -4,7 +4,7 @@ background-image: url('../../../../public/images/find-food/background.png'); background-repeat: no-repeat; background-position: 50% 100%; - + background-size: cover; padding: 132px + 78px 0 132px 0; } diff --git a/src/components/elements/FindFood/getDeliveryZone.js b/src/components/elements/FindFood/getDeliveryZone.js new file mode 100644 index 0000000..f9e0722 --- /dev/null +++ b/src/components/elements/FindFood/getDeliveryZone.js @@ -0,0 +1,27 @@ +const deliveryZone = [ + [30.216999863330468, 60.03220173420969], + [30.216999863330468, 59.90311247716717], + [30.174084519092194, 59.88051307052685], + [30.14833531254923, 59.86549577939312], + [30.158634995166427, 59.85686207237847], + [30.16138157719767, 59.846498655756214], + [30.1737411963383, 59.822996253346936], + [30.228672836963288, 59.825762105769186], + [30.280171250049218, 59.83786000127084], + [30.295277451221093, 59.82541638682978], + [30.320683335010166, 59.81158467638016], + [30.35158238286173, 59.81400564158892], + [30.40720066899455, 59.819538613797945], + [30.45114598149453, 59.837514408313794], + [30.502644394580486, 59.855480470604036], + [30.523930405322666, 59.88310157085963], + [30.52736363286172, 59.922766587193394], + [30.557576035205475, 59.96066257429594], + [30.505390976611732, 59.98544369552442], + [30.498524521533593, 60.027735082934754], + [30.493031357471104, 60.05658583325819], + [30.373555039111718, 60.06791313861288], + [30.216999863330468, 60.03220173420969], +]; + +export const reverseСoordinates = [deliveryZone.map((el) => el.reverse())]; diff --git a/src/components/elements/Header/DeliverAddress.tsx b/src/components/elements/Header/DeliverAddress.tsx index b28a2de..6cee94b 100644 --- a/src/components/elements/Header/DeliverAddress.tsx +++ b/src/components/elements/Header/DeliverAddress.tsx @@ -6,18 +6,18 @@ import { FC } from 'react'; import style from './deliverAddress.module.scss'; type DeliverAddressProps = { + address: string; classNames?: string; }; -export const DeliverAddress: FC = ({ classNames }) => { +export const DeliverAddress: FC = ({ address, classNames }) => { + console.log(address); return (

Deliver to:

Current Location - - Lakeshore Road East, Mississauga - + {address}
); }; diff --git a/src/components/elements/Header/Header.tsx b/src/components/elements/Header/Header.tsx index 3321fe1..83f510a 100644 --- a/src/components/elements/Header/Header.tsx +++ b/src/components/elements/Header/Header.tsx @@ -1,11 +1,12 @@ import cn from 'classnames'; import { FC } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { Link } from 'react-router-dom'; import { useNavigate } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import { useLocation } from 'react-router-dom'; import { ReactSVG } from 'react-svg'; +import { addressSelector } from '../../../store/slices/restaurants/slice'; import { isAuthSelector, removeUser } from '../../../store/slices/user/slice'; import { LogoType } from '../../ui/LogoType'; import { CartButton } from '../../ui/buttons/CartButton'; @@ -23,6 +24,8 @@ export const Header: FC = () => { const dispatch = useDispatch(); + const address = useSelector(addressSelector); + const handleLogOut = () => { dispatch(removeUser()); }; @@ -39,7 +42,7 @@ export const Header: FC = () => { - +
diff --git a/src/components/elements/Header/MobileMenu.tsx b/src/components/elements/Header/MobileMenu.tsx index 28307d1..bcd42a3 100644 --- a/src/components/elements/Header/MobileMenu.tsx +++ b/src/components/elements/Header/MobileMenu.tsx @@ -84,7 +84,7 @@ export const MobileMenu: FC = ({ handleLogOut }) => {
  • - +
  • diff --git a/src/components/pages/SearchPage/SearchPanel.tsx b/src/components/pages/SearchPage/SearchPanel.tsx index 03db11b..83179f7 100644 --- a/src/components/pages/SearchPage/SearchPanel.tsx +++ b/src/components/pages/SearchPage/SearchPanel.tsx @@ -93,6 +93,7 @@ export const SearchPanel: FC = () => { handleKeyDown={handleKeyDown} handleSearchValue={handleSearchValue} iconUrl={'/images/header/search.svg'} + placeholder={'Enter Your Request'} ref={searchRef} /> @@ -101,4 +102,4 @@ export const SearchPanel: FC = () => {
    ); -}; +}; \ No newline at end of file diff --git a/src/components/ui/TextInput/TextInput.tsx b/src/components/ui/TextInput/TextInput.tsx index ebcfc21..c852c9d 100644 --- a/src/components/ui/TextInput/TextInput.tsx +++ b/src/components/ui/TextInput/TextInput.tsx @@ -12,10 +12,11 @@ type TextInputProps = { handleKeyDown?: (event: KeyboardEvent) => void; handleSearchValue: (text: string) => void; iconUrl?: string; + placeholder: string; }; export const TextInput = forwardRef>( - ({ children, classNames, handleKeyDown, handleSearchValue, iconUrl }, ref) => { + ({ children, classNames, handleKeyDown, handleSearchValue, iconUrl, placeholder }, ref) => { const inputRef = useRef(null); const [value, setValue] = useState(''); @@ -25,7 +26,7 @@ export const TextInput = forwardRef { setValue(''); handleSearchValue(''); @@ -55,7 +56,7 @@ export const TextInput = forwardRef) { state.isLoaded = action.payload; }, + setLocation(state, action) { + state.location = action.payload; + }, + setPlacemarks(state) { + state.placemarks = state.list.map((item) => { + const { + address: { city, latitude, longitude, street_addr }, + backgroundId, + id, + logo_photos, + name, + phone_number, + } = item; + + return { + geometry: { + coordinates: [latitude, longitude], + type: 'Point', + }, + id, + properties: { + balloonContent: getBalloon( + id, + name, + logo_photos, + phone_number, + street_addr, + latitude, + longitude, + backgroundId, + city, + ), + }, + type: 'Feature', + }; + }); + }, }, }); @@ -35,6 +75,9 @@ export const restaurantListSelector = (state: RootStore) => state.restaurants.li export const errorSelector = (state: RootStore) => state.restaurants.error; export const isLoadedSelector = (state: RootStore) => state.restaurants.isLoaded; export const statusSelector = (state: RootStore) => state.restaurants.status; +export const placemarkSelector = (state: RootStore) => state.restaurants.placemarks; +export const addressSelector = (state: RootStore) => state.restaurants.location.address; +export const coordsSelector = (state: RootStore) => state.restaurants.location.coords; -export const { setLoaded } = restaurantsSlice.actions; +export const { setLoaded, setLocation, setPlacemarks } = restaurantsSlice.actions; export default restaurantsSlice.reducer; diff --git a/src/store/slices/restaurants/types.ts b/src/store/slices/restaurants/types.ts index 9da809a..711a123 100644 --- a/src/store/slices/restaurants/types.ts +++ b/src/store/slices/restaurants/types.ts @@ -4,5 +4,27 @@ export interface RestaurantSliceState { error: ErrorType; isLoaded: boolean; list: Restaurant[]; + location: Location; + placemarks: Placemark[]; status: Status; } +interface Placemark { + geometry: Geometry; + id: string; + properties: Properties; + type: string; +} + +type Geometry = { + coordinates: [number, number]; + type: string; +}; + +type Properties = { + balloonContent: string; +}; + +type Location = { + address: string; + coords: [number, number]; +}; diff --git a/src/utils/getAddress.js b/src/utils/getAddress.js new file mode 100644 index 0000000..2aadd03 --- /dev/null +++ b/src/utils/getAddress.js @@ -0,0 +1,5 @@ +export const getExactAddress = (response) => { + return response?.response?.GeoObjectCollection?.featureMember[0]['GeoObject']['metaDataProperty']['GeocoderMetaData'][ + 'Address' + ]['formatted']; +}; diff --git a/src/utils/getBalloon.js b/src/utils/getBalloon.js new file mode 100644 index 0000000..22b40ad --- /dev/null +++ b/src/utils/getBalloon.js @@ -0,0 +1,33 @@ +export const getBalloon = ( + id, + name, + logo_photos, + phone_number, + street_addr, + latitude, + longitude, + backgroundId, + city, +) => { + return `
    + + +
    ${name}
    +
    + + +
    ${city}, ${street_addr}
    +
    ${latitude.toFixed(5)}, ${longitude.toFixed(5)}
    +
    `; +}; From 3fa3e6432acb0e42da0590ef46f54d587866f41c Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Fri, 2 Feb 2024 13:38:34 +0300 Subject: [PATCH 04/22] updated --- src/components/elements/FindFood/YandexGeocoder.js | 4 +++- src/components/elements/Header/DeliverAddress.tsx | 1 - src/store/slices/restaurants/slice.ts | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/elements/FindFood/YandexGeocoder.js b/src/components/elements/FindFood/YandexGeocoder.js index 7ac5f08..a4a0a01 100644 --- a/src/components/elements/FindFood/YandexGeocoder.js +++ b/src/components/elements/FindFood/YandexGeocoder.js @@ -6,7 +6,9 @@ export class YandexGeocoder { async getAddressAndGeopoint(address) { try { - const response = await fetch(`${this.geocoder_url}?apikey=${this.api_key}&geocode=${address}&format=json`); + const response = await fetch( + `${this.geocoder_url}?apikey=${this.api_key}&geocode=${address}&format=json&lang=en_RU&results=5`, + ); const result = await response.json(); return result; diff --git a/src/components/elements/Header/DeliverAddress.tsx b/src/components/elements/Header/DeliverAddress.tsx index 6cee94b..e44176b 100644 --- a/src/components/elements/Header/DeliverAddress.tsx +++ b/src/components/elements/Header/DeliverAddress.tsx @@ -11,7 +11,6 @@ type DeliverAddressProps = { }; export const DeliverAddress: FC = ({ address, classNames }) => { - console.log(address); return (

    Deliver to:

    diff --git a/src/store/slices/restaurants/slice.ts b/src/store/slices/restaurants/slice.ts index ea94372..7576787 100644 --- a/src/store/slices/restaurants/slice.ts +++ b/src/store/slices/restaurants/slice.ts @@ -16,7 +16,10 @@ const initialState: RestaurantSliceState = { error: null, isLoaded: false, list: [], - location: { address: 'Санкт-Петербург, Шпалерная улица, 26', coords: [59.94971367493227, 30.35151817345885] }, + location: { + address: 'Saint Petersburg, Shpalernaya Street, 26', + coords: [59.94971367493227, 30.35151817345885], + }, placemarks: [], status: Status.LOADING, }; From 61c04772f9767062582d33c059cd9ec082880302 Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Fri, 2 Feb 2024 19:21:00 +0300 Subject: [PATCH 05/22] added moving map to geolocation point from input field --- src/components/elements/FindFood/Maps.js | 73 ++++++++++++++++---- src/components/elements/FindFood/balloon.css | 2 +- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 13c3adc..9a4c38b 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -1,5 +1,6 @@ /* eslint-disable max-len */ -import { GeolocationControl, Map, ObjectManager, Placemark, Polygon, YMaps } from '@pbe/react-yandex-maps'; +import { Map, ObjectManager, Placemark, Polygon, YMaps } from '@pbe/react-yandex-maps'; +import { useEffect, useState } from 'react'; import './balloon.css'; import { reverseСoordinates } from './getDeliveryZone'; @@ -10,26 +11,72 @@ const getMiniBalloon = (address) => `
    `; export const Maps = ({ address, geolocation, placemarks }) => { + const [maps, setMaps] = useState(null); + const [address2, setAddress] = useState(''); + const [coords, setCoords] = useState(geolocation); + + const getGeoLocation = (e) => { + console.log(e.get('target')); + const coord = e.get('target').getCenter(); + setCoords(coord); + + const resp = maps.geocode(coord); + resp.then((res) => { + setAddress(res.geoObjects.get(0).getAddressLine()); + }); + }; + + const onLoad = (map) => { + setMaps(map); + console.log(map?.geolocation.get()); + }; + const handleActionTick = (e) => { + // console.log(e.get('target')); + }; + useEffect(() => { + maps?.geocode(geolocation).then((res) => { + setAddress(res.geoObjects.get(0).getAddressLine()); + setCoords(geolocation); + console.log(coords); + console.log(address2); + }); + console.log(maps); + }, [geolocation, maps]); + return ( getGeoLocation(ymaps)} + onLoad={(ymaps) => onLoad(ymaps)} + state={{ center: coords }} > { }} geometry={reverseСoordinates} /> - {/* */} Date: Mon, 5 Feb 2024 18:19:07 +0300 Subject: [PATCH 06/22] updated marker for map --- src/components/elements/FindFood/Maps.js | 55 +++++++------------ src/components/elements/FindFood/balloon.css | 10 ++++ .../elements/FindFood/balloon.module.scss | 7 +++ 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 9a4c38b..85fc58d 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -11,16 +11,23 @@ const getMiniBalloon = (address) => `
    `; export const Maps = ({ address, geolocation, placemarks }) => { + console.log(geolocation); const [maps, setMaps] = useState(null); const [address2, setAddress] = useState(''); const [coords, setCoords] = useState(geolocation); const getGeoLocation = (e) => { - console.log(e.get('target')); const coord = e.get('target').getCenter(); setCoords(coord); - - const resp = maps.geocode(coord); + // const aa = e.get('target').panTo(coord, { + // delay: 1000, + // duration: 1000, + // flying: true, + // safe: true, + // timingFunction: 'ease-in-out', + // }); + setCoords(coord); + const resp = maps?.geocode(coord); resp.then((res) => { setAddress(res.geoObjects.get(0).getAddressLine()); }); @@ -28,19 +35,13 @@ export const Maps = ({ address, geolocation, placemarks }) => { const onLoad = (map) => { setMaps(map); - console.log(map?.geolocation.get()); - }; - const handleActionTick = (e) => { - // console.log(e.get('target')); }; + useEffect(() => { maps?.geocode(geolocation).then((res) => { setAddress(res.geoObjects.get(0).getAddressLine()); setCoords(geolocation); - console.log(coords); - console.log(address2); }); - console.log(maps); }, [geolocation, maps]); return ( @@ -51,14 +52,6 @@ export const Maps = ({ address, geolocation, placemarks }) => { }} > { 'geoObject.addon.balloon', 'control.GeolocationControl', ]} + state={{ + behaviors: ['default'], + center: coords, + controls: ['zoomControl', 'fullscreenControl', 'geolocationControl'], + zoom: 9, + }} className="map" - onActionTick={handleActionTick} onBoundsChange={(ymaps) => getGeoLocation(ymaps)} onLoad={(ymaps) => onLoad(ymaps)} - state={{ center: coords }} > +
    + pointer +
    { }} geometry={reverseСoordinates} /> - + Date: Mon, 5 Feb 2024 19:10:09 +0300 Subject: [PATCH 07/22] updated marker for map --- src/components/elements/FindFood/FindFood.tsx | 4 +- src/components/elements/FindFood/Maps.js | 53 ++++++++++++++----- src/components/elements/FindFood/balloon.css | 33 ++++++++++-- 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 0a9615b..209c645 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -46,7 +46,7 @@ export const FindFood: FC = () => { const handleSearchValue = (text: string) => { setSearchValue(text); }; - + console.log(searchValue); const yandexGeocoder = new YandexGeocoder(); useEffect(() => { @@ -83,7 +83,7 @@ export const FindFood: FC = () => {
    - + {/* */}
    diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 85fc58d..c39cf9d 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -1,6 +1,8 @@ /* eslint-disable max-len */ -import { Map, ObjectManager, Placemark, Polygon, YMaps } from '@pbe/react-yandex-maps'; +import { Map, ObjectManager, Polygon, YMaps } from '@pbe/react-yandex-maps'; +import cn from 'classnames'; import { useEffect, useState } from 'react'; +import { ReactSVG } from 'react-svg'; import './balloon.css'; import { reverseСoordinates } from './getDeliveryZone'; @@ -10,26 +12,28 @@ const getMiniBalloon = (address) => `
    ${address}
    `; -export const Maps = ({ address, geolocation, placemarks }) => { - console.log(geolocation); +export const Maps = ({ address, geolocation, placemarks, setSearchValue }) => { const [maps, setMaps] = useState(null); + const [isActive, setIsActive] = useState(null); const [address2, setAddress] = useState(''); const [coords, setCoords] = useState(geolocation); const getGeoLocation = (e) => { const coord = e.get('target').getCenter(); setCoords(coord); - // const aa = e.get('target').panTo(coord, { - // delay: 1000, - // duration: 1000, - // flying: true, - // safe: true, - // timingFunction: 'ease-in-out', - // }); + const aa = e.get('target').panTo(coord, { + delay: 1000, + duration: 1000, + flying: true, + safe: true, + timingFunction: 'ease-in-out', + }); setCoords(coord); const resp = maps?.geocode(coord); resp.then((res) => { setAddress(res.geoObjects.get(0).getAddressLine()); + setSearchValue(address2); + // console.log(res.geoObjects.get(0).geometry.getCoordinates()); }); }; @@ -44,6 +48,14 @@ export const Maps = ({ address, geolocation, placemarks }) => { }); }, [geolocation, maps]); + const handleActionTick = () => { + setIsActive(true); + }; + + const handleActionEnd = () => { + setIsActive(false); + }; + return ( { zoom: 9, }} className="map" + onActionEnd={handleActionEnd} + onActionTick={handleActionTick} onBoundsChange={(ymaps) => getGeoLocation(ymaps)} onLoad={(ymaps) => onLoad(ymaps)} > -
    - pointer +
    +
    + { preset: 'islands#redDotIcon', }} options={{ - clusterize: false, + clusterize: true, gridSize: 150, }} features={placemarks} @@ -101,3 +120,11 @@ export const Maps = ({ address, geolocation, placemarks }) => { ); }; +// // Инициализация карты из результата геокодирования +// var myMap; +// ymaps.geocode('Москва').then(function (res) { +// myMap = new ymaps.Map('map', { +// center: res.geoObjects.get(0).geometry.getCoordinates(), +// zoom : +// }); +// }); diff --git a/src/components/elements/FindFood/balloon.css b/src/components/elements/FindFood/balloon.css index ca3e753..e9d8276 100644 --- a/src/components/elements/FindFood/balloon.css +++ b/src/components/elements/FindFood/balloon.css @@ -30,9 +30,36 @@ .pointer { position: absolute; - top: 50%; + top: 48%; left: 50%; - width: 20px; - height: 30px; + transition: all 0.3s ease; z-index: 20; +} + +.placemark { + + transition: all 0.3s ease; + + path { + fill: #f75900; + } + + svg { + height: 30px; + width: 30px; + } +} + +.pointer.active { + + transition: all 0.3s ease; + transform: translateY(-2px); +} + +.placemark.active { + + path { + fill: #757575; + transition: all 0.3s ease; + } } \ No newline at end of file From cd574c0a7db3aa99181f462587a965c61d20af59 Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Tue, 6 Feb 2024 21:07:20 +0300 Subject: [PATCH 08/22] updated Maps --- src/components/elements/FindFood/FindFood.tsx | 26 +++--- src/components/elements/FindFood/Maps.js | 90 +++++++++++++------ src/components/elements/FindFood/balloon.css | 2 +- 3 files changed, 77 insertions(+), 41 deletions(-) diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 209c645..ebc10fe 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -47,20 +47,20 @@ export const FindFood: FC = () => { setSearchValue(text); }; console.log(searchValue); - const yandexGeocoder = new YandexGeocoder(); + // const yandexGeocoder = new YandexGeocoder(); - useEffect(() => { - if (requestText) - yandexGeocoder.getAddressAndGeopoint(requestText).then((res) => { - const [lon, lat] = getGeolocationCoordinates(res); - const address = getExactAddress(res); + // useEffect(() => { + // if (requestText) + // yandexGeocoder.getAddressAndGeopoint(requestText).then((res) => { + // const [lon, lat] = getGeolocationCoordinates(res); + // const address = getExactAddress(res); - if (address) { - setRequestText(address); - dispatch(setLocation({ address, coords: [lat, lon] })); - } - }); - }, [requestText]); + // if (address) { + // setRequestText(address); + // dispatch(setLocation({ address, coords: [lat, lon] })); + // } + // }); + // }, [requestText]); return (
    @@ -83,7 +83,7 @@ export const FindFood: FC = () => {
    - + {/* */} diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index c39cf9d..20a6552 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -1,9 +1,13 @@ /* eslint-disable max-len */ import { Map, ObjectManager, Polygon, YMaps } from '@pbe/react-yandex-maps'; import cn from 'classnames'; -import { useEffect, useState } from 'react'; +import debounce from 'lodash.debounce'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useSelector } from 'react-redux'; import { ReactSVG } from 'react-svg'; +import { useAppDispatch } from '../../../store'; +import { coordsSelector, setLocation } from '../../../store/slices/restaurants/slice'; import './balloon.css'; import { reverseСoordinates } from './getDeliveryZone'; @@ -12,50 +16,81 @@ const getMiniBalloon = (address) => `
    ${address}
    `; -export const Maps = ({ address, geolocation, placemarks, setSearchValue }) => { +let i = 0; + +export const Maps = ({ placemarks, requestText }) => { + console.log(i++); const [maps, setMaps] = useState(null); const [isActive, setIsActive] = useState(null); - const [address2, setAddress] = useState(''); - const [coords, setCoords] = useState(geolocation); + const [coord, setCoord] = useState(null); + const [address, setAddress] = useState(null); + // console.log(address); + + const dispatch = useAppDispatch(); + const coords = useSelector(coordsSelector); + console.log(coord); + + const updateSearchValue = useCallback( + debounce((coords) => { + setCoord(coords); + }, 50000), + [], + ); const getGeoLocation = (e) => { - const coord = e.get('target').getCenter(); - setCoords(coord); - const aa = e.get('target').panTo(coord, { - delay: 1000, - duration: 1000, - flying: true, - safe: true, - timingFunction: 'ease-in-out', - }); - setCoords(coord); - const resp = maps?.geocode(coord); - resp.then((res) => { - setAddress(res.geoObjects.get(0).getAddressLine()); - setSearchValue(address2); - // console.log(res.geoObjects.get(0).geometry.getCoordinates()); - }); + const coords = e.get('target').getCenter(); + updateSearchValue(coords); + + // const resp = maps?.geocode(coord); + // resp.then((res) => { + // setAddress(res.geoObjects.get(0).getAddressLine()); + // dispatch(setLocation({ address, coords })); + // }); }; const onLoad = (map) => { setMaps(map); }; + // console.log(requestText); + + useEffect(() => { + if (requestText) + maps?.geocode(requestText).then((res) => { + const coords = res.geoObjects.get(0).geometry.getCoordinates(); + const address = res.geoObjects.get(0).getAddressLine(); + setCoord(coords); + setLocation(address); + dispatch(setLocation({ address, coords })); + }); + }, [requestText]); useEffect(() => { - maps?.geocode(geolocation).then((res) => { - setAddress(res.geoObjects.get(0).getAddressLine()); - setCoords(geolocation); - }); - }, [geolocation, maps]); + if (coord) { + const resp = maps?.geocode(coord); + resp.then((res) => { + setAddress(res.geoObjects.get(0).getAddressLine()); + dispatch(setLocation({ address, coords })); + }); + mapRef?.current?.panTo(coord, { + checkZoomRange: true, + delay: 1000, + duration: 500, + flying: true, + timingFunction: 'ease', + }); + } + }, [coord]); const handleActionTick = () => { setIsActive(true); }; - const handleActionEnd = () => { + const handleActionEnd = (e) => { setIsActive(false); }; + const mapRef = useRef(); + return ( { behaviors: ['default'], center: coords, controls: ['zoomControl', 'fullscreenControl', 'geolocationControl'], - zoom: 9, + zoom: 15, }} className="map" + instanceRef={mapRef} onActionEnd={handleActionEnd} onActionTick={handleActionTick} onBoundsChange={(ymaps) => getGeoLocation(ymaps)} diff --git a/src/components/elements/FindFood/balloon.css b/src/components/elements/FindFood/balloon.css index e9d8276..35d3988 100644 --- a/src/components/elements/FindFood/balloon.css +++ b/src/components/elements/FindFood/balloon.css @@ -53,7 +53,7 @@ .pointer.active { transition: all 0.3s ease; - transform: translateY(-2px); + transform: translateY(-15px); } .placemark.active { From 1ff8d756ca17b33164304df0c567d87b4243fce4 Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Thu, 8 Feb 2024 14:23:50 +0300 Subject: [PATCH 09/22] added locationSlice --- src/components/elements/FindFood/FindFood.tsx | 50 +++++---- src/components/elements/FindFood/Maps.js | 7 +- src/store/index.ts | 2 + src/store/slices/index.ts | 1 + src/store/slices/location/slice.js | 103 ++++++++++++++++++ src/store/slices/location/types.js | 0 src/utils/getGeolocationCoordinates.js | 9 +- 7 files changed, 149 insertions(+), 23 deletions(-) create mode 100644 src/store/slices/location/slice.js create mode 100644 src/store/slices/location/types.js diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index ebc10fe..db936d7 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -4,6 +4,7 @@ import { FC, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { useAppDispatch } from '../../../store'; +import { fetchLocation, isLoadedSelector, locationListSelector } from '../../../store/slices/location/slice'; import { addressSelector, coordsSelector, @@ -14,6 +15,7 @@ import { } from '../../../store/slices/restaurants/slice'; import { getExactAddress } from '../../../utils/getAddress'; import { getGeolocationCoordinates } from '../../../utils/getGeolocationCoordinates'; +import { Popup } from '../../pages/SearchPage/Popup'; import { TextInput } from '../../ui/TextInput'; import { SearchButton } from '../../ui/buttons/SearchButton'; import { DeliveryMethod } from './DeliveryMethod'; @@ -28,10 +30,17 @@ export const FindFood: FC = () => { const [searchValue, setSearchValue] = useState(''); const [requestText, setRequestText] = useState(''); - const list = useSelector(restaurantListSelector); + const list = useSelector(locationListSelector); const placemarks = useSelector(placemarkSelector); const coords = useSelector(coordsSelector); const address = useSelector(addressSelector); + console.log(list); + + const [visiblePopup, setVisiblePopup] = useState(false); + const popupRef = useRef(null); + const isLoaded = useSelector(isLoadedSelector); + console.log(isLoaded); + console.log(visiblePopup); useEffect(() => { if (list.length) { @@ -39,28 +48,21 @@ export const FindFood: FC = () => { } }, [list]); - const handleSearch = () => { - setRequestText(searchValue); - }; + // const handleSearch = () => { + // setRequestText(searchValue); + // }; const handleSearchValue = (text: string) => { setSearchValue(text); }; - console.log(searchValue); - // const yandexGeocoder = new YandexGeocoder(); - // useEffect(() => { - // if (requestText) - // yandexGeocoder.getAddressAndGeopoint(requestText).then((res) => { - // const [lon, lat] = getGeolocationCoordinates(res); - // const address = getExactAddress(res); + // console.log(searchValue); - // if (address) { - // setRequestText(address); - // dispatch(setLocation({ address, coords: [lat, lon] })); - // } - // }); - // }, [requestText]); + useEffect(() => { + if (searchValue) { + dispatch(fetchLocation({ searchValue })); + } + }, [searchValue]); return (
    @@ -81,10 +83,20 @@ export const FindFood: FC = () => { > - + - {/* */} + {/* */} + {list.map((el: any) => ( +
    + {el.address} {el.coords} +
    + ))} diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 20a6552..d4416e1 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -16,10 +16,10 @@ const getMiniBalloon = (address) => `
    ${address}
    `; -let i = 0; +const i = 0; export const Maps = ({ placemarks, requestText }) => { - console.log(i++); + // console.log(i++); const [maps, setMaps] = useState(null); const [isActive, setIsActive] = useState(null); @@ -29,7 +29,7 @@ export const Maps = ({ placemarks, requestText }) => { const dispatch = useAppDispatch(); const coords = useSelector(coordsSelector); - console.log(coord); + // console.log(coord); const updateSearchValue = useCallback( debounce((coords) => { @@ -156,6 +156,7 @@ export const Maps = ({ placemarks, requestText }) => { ); }; + // // Инициализация карты из результата геокодирования // var myMap; // ymaps.geocode('Москва').then(function (res) { diff --git a/src/store/index.ts b/src/store/index.ts index fd1924b..a251c39 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -6,6 +6,7 @@ import storageSession from 'reduxjs-toolkit-persist/lib/storage/session'; import { cart, filters, + location, product, products, productsFastAccess, @@ -26,6 +27,7 @@ const persistConfig = { const rootReducer = combineReducers({ cart, filters, + location, product, products, productsFastAccess, diff --git a/src/store/slices/index.ts b/src/store/slices/index.ts index 554c02f..d71cfca 100644 --- a/src/store/slices/index.ts +++ b/src/store/slices/index.ts @@ -1,5 +1,6 @@ export { default as cart } from './cart/slice'; export { default as filters } from './filters/slice'; +export { default as location } from './location/slice'; export { default as product } from './product/slice'; export { default as products } from './products/slice'; export { default as productsFastAccess } from './productsFastAccess/slice'; diff --git a/src/store/slices/location/slice.js b/src/store/slices/location/slice.js new file mode 100644 index 0000000..e37cc8f --- /dev/null +++ b/src/store/slices/location/slice.js @@ -0,0 +1,103 @@ +import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import axios from 'axios'; + +// import { RootStore } from '../..'; +import { getBalloon } from '../../../utils/getBalloon'; +import { getGeolocationCoordinates } from '../../../utils/getGeolocationCoordinates'; +// import { fetchRestaurantsData } from '../../utils/fetchRestaurantsData'; +import { CustomErrors, MyAsyncThunkConfig, Restaurant, Status, getExtraReducers } from '../../utils/getExtraReducers'; +// import { FiltersForRestaurants } from '../../utils/getFilterForRestaurants'; +// import { RestaurantSliceState } from './types'; + +export const fetchData = async function ({ searchValue }, { rejectWithValue }) { + try { + const { data } = await axios.get( + `https://geocode-maps.yandex.ru/1.x?apikey=${process.env.REACT_APP_YANDEX_API_KEY}&geocode=${searchValue}&format=json&lang=en_RU&results=5`, + ); + + if (data.length === 0) { + return rejectWithValue(CustomErrors.ERROR_NOTHING_FOUND); + } + + return getGeolocationCoordinates(data); + } catch (error) { + if (error.toJSON().status === 404) { + return rejectWithValue(CustomErrors.ERROR_NOTHING_FOUND); + } + return rejectWithValue('Error: ' + error?.message); + } +}; +export const fetchLocation = createAsyncThunk('location/fetchLocation', fetchData); + +const initialState = { + error: null, + isLoaded: false, + list: [], + location: { + address: 'Saint Petersburg, Shpalernaya Street, 26', + coords: [59.94971367493227, 30.35151817345885], + }, + placemarks: [], + status: Status.LOADING, +}; + +const locationSlice = createSlice({ + extraReducers: (builder) => getExtraReducers(builder)(fetchLocation), + + initialState, + name: 'location', + + reducers: { + setLoaded(state, action) { + state.isLoaded = action.payload; + }, + setLocation(state, action) { + state.location = action.payload; + }, + setPlacemarks(state) { + state.placemarks = state.list.map((item) => { + const { + address: { city, latitude, longitude, street_addr }, + backgroundId, + id, + logo_photos, + name, + phone_number, + } = item; + + return { + geometry: { + coordinates: [latitude, longitude], + type: 'Point', + }, + id, + properties: { + balloonContent: getBalloon( + id, + name, + logo_photos, + phone_number, + street_addr, + latitude, + longitude, + backgroundId, + city, + ), + }, + type: 'Feature', + }; + }); + }, + }, +}); + +export const locationListSelector = (state) => state.location.list; +export const errorSelector = (state) => state.location.error; +export const isLoadedSelector = (state) => state.location.isLoaded; +export const statusSelector = (state) => state.location.status; +export const placemarkSelector = (state) => state.location.placemarks; +export const addressSelector = (state) => state.location.location.address; +export const coordsSelector = (state) => state.location.location.coords; + +export const { setLoaded, setLocation, setPlacemarks } = locationSlice.actions; +export default locationSlice.reducer; diff --git a/src/store/slices/location/types.js b/src/store/slices/location/types.js new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/getGeolocationCoordinates.js b/src/utils/getGeolocationCoordinates.js index 4c1d56d..9340ffe 100644 --- a/src/utils/getGeolocationCoordinates.js +++ b/src/utils/getGeolocationCoordinates.js @@ -1,3 +1,10 @@ export const getGeolocationCoordinates = (response) => { - return response?.response?.GeoObjectCollection?.featureMember[0]['GeoObject']['Point']['pos'].split(' '); + // return response?.response?.GeoObjectCollection?.featureMember[0]['GeoObject']['Point']['pos'].split(' '); + return response?.response?.GeoObjectCollection?.featureMember.map((el) => { + const geoObject = el?.GeoObject; + return { + address: geoObject?.metaDataProperty?.GeocoderMetaData?.Address?.formatted, + coords: geoObject?.Point?.pos.split(' ').reverse(), + }; + }); }; From 19d802d7746b0456082aece2ecf29d47f2b1aeee Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Thu, 8 Feb 2024 16:12:32 +0300 Subject: [PATCH 10/22] refactored --- src/components/elements/FindFood/FindFood.tsx | 44 ++++++++------- src/components/elements/FindFood/Maps.js | 56 +++++++++++-------- src/store/slices/location/slice.js | 3 +- 3 files changed, 57 insertions(+), 46 deletions(-) diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index db936d7..8a89755 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -2,15 +2,20 @@ import { faLocationDot } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FC, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; +import { Link } from 'react-router-dom'; import { useAppDispatch } from '../../../store'; -import { fetchLocation, isLoadedSelector, locationListSelector } from '../../../store/slices/location/slice'; +import { + fetchLocation, + isLoadedSelector, + locationListSelector, + setLocation, +} from '../../../store/slices/location/slice'; import { addressSelector, coordsSelector, placemarkSelector, restaurantListSelector, - setLocation, setPlacemarks, } from '../../../store/slices/restaurants/slice'; import { getExactAddress } from '../../../utils/getAddress'; @@ -20,7 +25,6 @@ import { TextInput } from '../../ui/TextInput'; import { SearchButton } from '../../ui/buttons/SearchButton'; import { DeliveryMethod } from './DeliveryMethod'; import { Maps } from './Maps'; -import { YandexGeocoder } from './YandexGeocoder'; import style from './findFood.module.scss'; export const FindFood: FC = () => { @@ -34,13 +38,13 @@ export const FindFood: FC = () => { const placemarks = useSelector(placemarkSelector); const coords = useSelector(coordsSelector); const address = useSelector(addressSelector); - console.log(list); + // console.log(list); const [visiblePopup, setVisiblePopup] = useState(false); const popupRef = useRef(null); const isLoaded = useSelector(isLoadedSelector); - console.log(isLoaded); - console.log(visiblePopup); + // console.log(isLoaded); + // console.log(visiblePopup); useEffect(() => { if (list.length) { @@ -48,9 +52,7 @@ export const FindFood: FC = () => { } }, [list]); - // const handleSearch = () => { - // setRequestText(searchValue); - // }; + const handleSearch = () => {}; const handleSearchValue = (text: string) => { setSearchValue(text); @@ -64,6 +66,10 @@ export const FindFood: FC = () => { } }, [searchValue]); + const handleChangeLocation = (el: any) => { + dispatch(setLocation(el)); + }; + return (
    @@ -83,20 +89,18 @@ export const FindFood: FC = () => { > - +
    {/* */} - {list.map((el: any) => ( -
    - {el.address} {el.coords} -
    - ))} +
      + {list.map((el: any, i: any) => ( +
    • handleChangeLocation(el)}> + {' '} + {el.address} {el.coords} +
    • + ))} +
    diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index d4416e1..6adbe54 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -7,7 +7,7 @@ import { useSelector } from 'react-redux'; import { ReactSVG } from 'react-svg'; import { useAppDispatch } from '../../../store'; -import { coordsSelector, setLocation } from '../../../store/slices/restaurants/slice'; +import { coordsSelector, setLocation } from '../../../store/slices/location/slice'; import './balloon.css'; import { reverseСoordinates } from './getDeliveryZone'; @@ -22,7 +22,8 @@ export const Maps = ({ placemarks, requestText }) => { // console.log(i++); const [maps, setMaps] = useState(null); const [isActive, setIsActive] = useState(null); - + const mapRef = useRef(); + const [isLoaded, setIsLoaded] = useState(false); const [coord, setCoord] = useState(null); const [address, setAddress] = useState(null); // console.log(address); @@ -34,7 +35,7 @@ export const Maps = ({ placemarks, requestText }) => { const updateSearchValue = useCallback( debounce((coords) => { setCoord(coords); - }, 50000), + }, 500), [], ); const getGeoLocation = (e) => { @@ -53,43 +54,49 @@ export const Maps = ({ placemarks, requestText }) => { }; // console.log(requestText); - useEffect(() => { - if (requestText) - maps?.geocode(requestText).then((res) => { - const coords = res.geoObjects.get(0).geometry.getCoordinates(); - const address = res.geoObjects.get(0).getAddressLine(); - setCoord(coords); - setLocation(address); - dispatch(setLocation({ address, coords })); - }); - }, [requestText]); + // useEffect(() => { + // if (requestText) + // maps?.geocode(requestText).then((res) => { + // const coords = res.geoObjects.get(0).geometry.getCoordinates(); + // const address = res.geoObjects.get(0).getAddressLine(); + // setCoord(coords); + // setLocation(address); + // dispatch(setLocation({ address, coords })); + // }); + // }, [requestText]); useEffect(() => { if (coord) { + setIsLoaded(false); + const resp = maps?.geocode(coord); resp.then((res) => { + setIsLoaded(true); setAddress(res.geoObjects.get(0).getAddressLine()); dispatch(setLocation({ address, coords })); }); - mapRef?.current?.panTo(coord, { - checkZoomRange: true, - delay: 1000, - duration: 500, - flying: true, - timingFunction: 'ease', - }); + // mapRef?.current?.panTo(coord, { + // checkZoomRange: true, + // delay: 1000, + // duration: 500, + // flying: true, + // timingFunction: 'ease', + // }); } }, [coord]); - const handleActionTick = () => { + // useEffect(() => { + // maps?.panTo(coords); + // }, [maps]); + + const handleActionBegin = () => { setIsActive(true); }; const handleActionEnd = (e) => { setIsActive(false); }; - - const mapRef = useRef(); + console.log(isActive); return ( { }} className="map" instanceRef={mapRef} + onActionBegin={handleActionBegin} onActionEnd={handleActionEnd} - onActionTick={handleActionTick} onBoundsChange={(ymaps) => getGeoLocation(ymaps)} onLoad={(ymaps) => onLoad(ymaps)} > @@ -126,6 +133,7 @@ export const Maps = ({ placemarks, requestText }) => { src={`${process.env.PUBLIC_URL}/images/find-food/search-panel/location.svg`} wrapper="span" /> + {!isLoaded && 'd'} Date: Thu, 8 Feb 2024 18:08:16 +0300 Subject: [PATCH 11/22] refactored --- src/components/elements/FindFood/FindFood.tsx | 7 +- src/components/elements/FindFood/Maps.js | 66 ++++++------------- src/components/elements/FindFood/balloon.css | 18 ++++- src/components/elements/Header/Header.tsx | 2 +- 4 files changed, 41 insertions(+), 52 deletions(-) diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 8a89755..542b4a3 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -32,7 +32,6 @@ export const FindFood: FC = () => { const dispatch = useAppDispatch(); const [searchValue, setSearchValue] = useState(''); - const [requestText, setRequestText] = useState(''); const list = useSelector(locationListSelector); const placemarks = useSelector(placemarkSelector); @@ -58,8 +57,6 @@ export const FindFood: FC = () => { setSearchValue(text); }; - // console.log(searchValue); - useEffect(() => { if (searchValue) { dispatch(fetchLocation({ searchValue })); @@ -91,13 +88,13 @@ export const FindFood: FC = () => { - + {/* */}
      {list.map((el: any, i: any) => (
    • handleChangeLocation(el)}> {' '} - {el.address} {el.coords} + {el.address} {el.coords[0]} {el.coords[1]}
    • ))}
    diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 6adbe54..592ad08 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -7,7 +7,9 @@ import { useSelector } from 'react-redux'; import { ReactSVG } from 'react-svg'; import { useAppDispatch } from '../../../store'; -import { coordsSelector, setLocation } from '../../../store/slices/location/slice'; +import { addressSelector, coordsSelector, setLocation } from '../../../store/slices/location/slice'; +import { placemarkSelector } from '../../../store/slices/restaurants/slice'; +import { getBalloon } from '../../../utils/getBalloon'; import './balloon.css'; import { reverseСoordinates } from './getDeliveryZone'; @@ -16,22 +18,21 @@ const getMiniBalloon = (address) => `
    ${address}
    `; -const i = 0; - -export const Maps = ({ placemarks, requestText }) => { - // console.log(i++); +export const Maps = () => { const [maps, setMaps] = useState(null); const [isActive, setIsActive] = useState(null); - const mapRef = useRef(); - const [isLoaded, setIsLoaded] = useState(false); + const [visibleBalloon, setVisibleBalloon] = useState(false); + const [isLoaded, setIsLoaded] = useState(true); const [coord, setCoord] = useState(null); - const [address, setAddress] = useState(null); - // console.log(address); + // const [address, setAddress] = useState(null); + const placemarks = useSelector(placemarkSelector); const dispatch = useAppDispatch(); const coords = useSelector(coordsSelector); - // console.log(coord); + const address = useSelector(addressSelector); + console.log(coords); + console.log(address); const updateSearchValue = useCallback( debounce((coords) => { setCoord(coords); @@ -41,54 +42,27 @@ export const Maps = ({ placemarks, requestText }) => { const getGeoLocation = (e) => { const coords = e.get('target').getCenter(); updateSearchValue(coords); - - // const resp = maps?.geocode(coord); - // resp.then((res) => { - // setAddress(res.geoObjects.get(0).getAddressLine()); - // dispatch(setLocation({ address, coords })); - // }); }; const onLoad = (map) => { setMaps(map); }; - // console.log(requestText); - - // useEffect(() => { - // if (requestText) - // maps?.geocode(requestText).then((res) => { - // const coords = res.geoObjects.get(0).geometry.getCoordinates(); - // const address = res.geoObjects.get(0).getAddressLine(); - // setCoord(coords); - // setLocation(address); - // dispatch(setLocation({ address, coords })); - // }); - // }, [requestText]); useEffect(() => { - if (coord) { + if (coord?.length) { setIsLoaded(false); const resp = maps?.geocode(coord); resp.then((res) => { setIsLoaded(true); - setAddress(res.geoObjects.get(0).getAddressLine()); - dispatch(setLocation({ address, coords })); + // setAddress(res.geoObjects.get(0).getAddressLine()); + dispatch(setLocation({ address: res.geoObjects.get(0).getAddressLine(), coords: coord })); + // handleChangeAddress(address); + // console.log(address); }); - // mapRef?.current?.panTo(coord, { - // checkZoomRange: true, - // delay: 1000, - // duration: 500, - // flying: true, - // timingFunction: 'ease', - // }); } }, [coord]); - // useEffect(() => { - // maps?.panTo(coords); - // }, [maps]); - const handleActionBegin = () => { setIsActive(true); }; @@ -96,7 +70,6 @@ export const Maps = ({ placemarks, requestText }) => { const handleActionEnd = (e) => { setIsActive(false); }; - console.log(isActive); return ( { zoom: 15, }} className="map" - instanceRef={mapRef} onActionBegin={handleActionBegin} onActionEnd={handleActionEnd} onBoundsChange={(ymaps) => getGeoLocation(ymaps)} onLoad={(ymaps) => onLoad(ymaps)} > -
    +
    setVisibleBalloon(true)}> { features={placemarks} modules={['objectManager.addon.objectsBalloon', 'objectManager.addon.objectsHint']} /> +
    +
    Your location
    +
    {address}
    +
    ); diff --git a/src/components/elements/FindFood/balloon.css b/src/components/elements/FindFood/balloon.css index 35d3988..951149c 100644 --- a/src/components/elements/FindFood/balloon.css +++ b/src/components/elements/FindFood/balloon.css @@ -1,4 +1,11 @@ -.balloon {} +.balloon { + background-color: aliceblue; + position: absolute; + top: 60%; + left: 60%; + z-index: 10; + max-width: 300px; +} .balloon__link {} @@ -34,6 +41,7 @@ left: 50%; transition: all 0.3s ease; z-index: 20; + cursor: pointer; } .placemark { @@ -62,4 +70,12 @@ fill: #757575; transition: all 0.3s ease; } +} + +.balloon { + display: none; +} + +.visible { + display: block; } \ No newline at end of file diff --git a/src/components/elements/Header/Header.tsx b/src/components/elements/Header/Header.tsx index 83f510a..58e6c11 100644 --- a/src/components/elements/Header/Header.tsx +++ b/src/components/elements/Header/Header.tsx @@ -6,7 +6,7 @@ import { Link } from 'react-router-dom'; import { useLocation } from 'react-router-dom'; import { ReactSVG } from 'react-svg'; -import { addressSelector } from '../../../store/slices/restaurants/slice'; +import { addressSelector } from '../../../store/slices/location/slice'; import { isAuthSelector, removeUser } from '../../../store/slices/user/slice'; import { LogoType } from '../../ui/LogoType'; import { CartButton } from '../../ui/buttons/CartButton'; From 67baf9308bd159ba3fafb0c5607247386fd6d58b Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Thu, 8 Feb 2024 18:17:30 +0300 Subject: [PATCH 12/22] refactored --- src/components/elements/FindFood/FindFood.tsx | 20 +++------------- src/components/elements/FindFood/Maps.js | 23 +------------------ src/components/ui/TextInput/TextInput.tsx | 13 ++++++++--- 3 files changed, 14 insertions(+), 42 deletions(-) diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 542b4a3..1706220 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -2,25 +2,16 @@ import { faLocationDot } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FC, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; -import { Link } from 'react-router-dom'; import { useAppDispatch } from '../../../store'; import { + addressSelector, fetchLocation, isLoadedSelector, locationListSelector, setLocation, } from '../../../store/slices/location/slice'; -import { - addressSelector, - coordsSelector, - placemarkSelector, - restaurantListSelector, - setPlacemarks, -} from '../../../store/slices/restaurants/slice'; -import { getExactAddress } from '../../../utils/getAddress'; -import { getGeolocationCoordinates } from '../../../utils/getGeolocationCoordinates'; -import { Popup } from '../../pages/SearchPage/Popup'; +import { setPlacemarks } from '../../../store/slices/restaurants/slice'; import { TextInput } from '../../ui/TextInput'; import { SearchButton } from '../../ui/buttons/SearchButton'; import { DeliveryMethod } from './DeliveryMethod'; @@ -34,16 +25,11 @@ export const FindFood: FC = () => { const [searchValue, setSearchValue] = useState(''); const list = useSelector(locationListSelector); - const placemarks = useSelector(placemarkSelector); - const coords = useSelector(coordsSelector); const address = useSelector(addressSelector); - // console.log(list); const [visiblePopup, setVisiblePopup] = useState(false); const popupRef = useRef(null); const isLoaded = useSelector(isLoadedSelector); - // console.log(isLoaded); - // console.log(visiblePopup); useEffect(() => { if (list.length) { @@ -79,6 +65,7 @@ export const FindFood: FC = () => {
    {
    - {/* */}
      {list.map((el: any, i: any) => (
    • handleChangeLocation(el)}> diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 592ad08..2a46a06 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -2,37 +2,28 @@ import { Map, ObjectManager, Polygon, YMaps } from '@pbe/react-yandex-maps'; import cn from 'classnames'; import debounce from 'lodash.debounce'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { ReactSVG } from 'react-svg'; import { useAppDispatch } from '../../../store'; import { addressSelector, coordsSelector, setLocation } from '../../../store/slices/location/slice'; import { placemarkSelector } from '../../../store/slices/restaurants/slice'; -import { getBalloon } from '../../../utils/getBalloon'; import './balloon.css'; import { reverseСoordinates } from './getDeliveryZone'; -const getMiniBalloon = (address) => `
      -
      Your location
      -
      ${address}
      -
      `; - export const Maps = () => { const [maps, setMaps] = useState(null); const [isActive, setIsActive] = useState(null); const [visibleBalloon, setVisibleBalloon] = useState(false); const [isLoaded, setIsLoaded] = useState(true); const [coord, setCoord] = useState(null); - // const [address, setAddress] = useState(null); const placemarks = useSelector(placemarkSelector); const dispatch = useAppDispatch(); const coords = useSelector(coordsSelector); const address = useSelector(addressSelector); - console.log(coords); - console.log(address); const updateSearchValue = useCallback( debounce((coords) => { setCoord(coords); @@ -55,10 +46,7 @@ export const Maps = () => { const resp = maps?.geocode(coord); resp.then((res) => { setIsLoaded(true); - // setAddress(res.geoObjects.get(0).getAddressLine()); dispatch(setLocation({ address: res.geoObjects.get(0).getAddressLine(), coords: coord })); - // handleChangeAddress(address); - // console.log(address); }); } }, [coord]); @@ -140,12 +128,3 @@ export const Maps = () => { ); }; - -// // Инициализация карты из результата геокодирования -// var myMap; -// ymaps.geocode('Москва').then(function (res) { -// myMap = new ymaps.Map('map', { -// center: res.geoObjects.get(0).geometry.getCoordinates(), -// zoom : -// }); -// }); diff --git a/src/components/ui/TextInput/TextInput.tsx b/src/components/ui/TextInput/TextInput.tsx index c852c9d..6acec96 100644 --- a/src/components/ui/TextInput/TextInput.tsx +++ b/src/components/ui/TextInput/TextInput.tsx @@ -1,13 +1,14 @@ /* eslint-disable max-len */ import cn from 'classnames'; import debounce from 'lodash.debounce'; -import { KeyboardEvent, forwardRef, useCallback, useRef, useState } from 'react'; +import { KeyboardEvent, forwardRef, useCallback, useEffect, useRef, useState } from 'react'; import { ChangeEvent, PropsWithChildren } from 'react'; import { ReactSVG } from 'react-svg'; import style from './textInput.module.scss'; type TextInputProps = { + address?: string; classNames?: string; handleKeyDown?: (event: KeyboardEvent) => void; handleSearchValue: (text: string) => void; @@ -16,7 +17,7 @@ type TextInputProps = { }; export const TextInput = forwardRef>( - ({ children, classNames, handleKeyDown, handleSearchValue, iconUrl, placeholder }, ref) => { + ({ address, children, classNames, handleKeyDown, handleSearchValue, iconUrl, placeholder }, ref) => { const inputRef = useRef(null); const [value, setValue] = useState(''); @@ -44,7 +45,13 @@ export const TextInput = forwardRef { + if (address) { + setValue(address); + updateSearchValue(address); + } + }, [address]); + console.log(value); return (
      Date: Fri, 9 Feb 2024 18:21:40 +0300 Subject: [PATCH 13/22] refactored --- src/components/elements/FindFood/FindFood.tsx | 53 ++++++++++++++----- src/components/elements/FindFood/Maps.js | 47 ++++++++-------- src/components/elements/FindFood/balloon.css | 13 +++-- .../elements/FindFood/findFood.module.scss | 4 ++ .../Header/deliverAddress.module.scss | 1 + src/components/ui/TextInput/TextInput.tsx | 4 +- src/store/slices/location/slice.js | 4 +- 7 files changed, 78 insertions(+), 48 deletions(-) diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 1706220..66bda48 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -1,5 +1,7 @@ -import { faLocationDot } from '@fortawesome/free-solid-svg-icons'; +import { faL, faLocationDot } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Placemark } from '@pbe/react-yandex-maps'; +import cn from 'classnames'; import { FC, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; @@ -11,7 +13,7 @@ import { locationListSelector, setLocation, } from '../../../store/slices/location/slice'; -import { setPlacemarks } from '../../../store/slices/restaurants/slice'; +import { placemarkSelector, restaurantListSelector, setPlacemarks } from '../../../store/slices/restaurants/slice'; import { TextInput } from '../../ui/TextInput'; import { SearchButton } from '../../ui/buttons/SearchButton'; import { DeliveryMethod } from './DeliveryMethod'; @@ -30,29 +32,48 @@ export const FindFood: FC = () => { const [visiblePopup, setVisiblePopup] = useState(false); const popupRef = useRef(null); const isLoaded = useSelector(isLoadedSelector); + const placemarks = useSelector(placemarkSelector); + const listRest = useSelector(restaurantListSelector); + + const [place, setPlace] = useState('Saint Petersburg, Shpalernaya Street, 26'); + const [coord, setCoord] = useState([59.94971367493227, 30.35151817345885]); useEffect(() => { - if (list.length) { + if (listRest.length) { dispatch(setPlacemarks()); } - }, [list]); + }, [listRest]); - const handleSearch = () => {}; + const handleSearch = () => { + dispatch(setLocation({ address: place, coords: coord })); + }; const handleSearchValue = (text: string) => { setSearchValue(text); }; + const handleChangeCoord = (coord: any) => { + setCoord(coord); + }; + + const handleChangeAddress = (address: any) => { + setPlace(address); + }; + + const handleChangeLocation = ({ address, coords }: any) => { + setPlace(address); + setCoord(coords); + setVisiblePopup(false); + }; + useEffect(() => { if (searchValue) { dispatch(fetchLocation({ searchValue })); + setVisiblePopup(true); + console.log(visiblePopup); } }, [searchValue]); - const handleChangeLocation = (el: any) => { - dispatch(setLocation(el)); - }; - return (
      @@ -65,7 +86,7 @@ export const FindFood: FC = () => {
      {
      - -
        + {searchValue && ( + + )} +
          {list.map((el: any, i: any) => (
        • handleChangeLocation(el)}> {' '} diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 2a46a06..006f476 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -3,36 +3,26 @@ import { Map, ObjectManager, Polygon, YMaps } from '@pbe/react-yandex-maps'; import cn from 'classnames'; import debounce from 'lodash.debounce'; import { useCallback, useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; import { ReactSVG } from 'react-svg'; -import { useAppDispatch } from '../../../store'; -import { addressSelector, coordsSelector, setLocation } from '../../../store/slices/location/slice'; -import { placemarkSelector } from '../../../store/slices/restaurants/slice'; import './balloon.css'; import { reverseСoordinates } from './getDeliveryZone'; -export const Maps = () => { +export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, placemarks }) => { const [maps, setMaps] = useState(null); const [isActive, setIsActive] = useState(null); const [visibleBalloon, setVisibleBalloon] = useState(false); const [isLoaded, setIsLoaded] = useState(true); - const [coord, setCoord] = useState(null); - const placemarks = useSelector(placemarkSelector); - - const dispatch = useAppDispatch(); - const coords = useSelector(coordsSelector); - const address = useSelector(addressSelector); const updateSearchValue = useCallback( - debounce((coords) => { - setCoord(coords); + debounce((coord) => { + handleChangeCoord(coord); }, 500), [], ); const getGeoLocation = (e) => { - const coords = e.get('target').getCenter(); - updateSearchValue(coords); + const coord = e.get('target').getCenter(); + updateSearchValue(coord); }; const onLoad = (map) => { @@ -40,14 +30,17 @@ export const Maps = () => { }; useEffect(() => { - if (coord?.length) { + if (maps && coord?.length) { setIsLoaded(false); - const resp = maps?.geocode(coord); - resp.then((res) => { - setIsLoaded(true); - dispatch(setLocation({ address: res.geoObjects.get(0).getAddressLine(), coords: coord })); - }); + resp + .then((res) => { + setIsLoaded(true); + handleChangeAddress(res.geoObjects.get(0).getAddressLine()); + }) + .catch((error) => { + console.error('The Promise is rejected!', error); + }); } }, [coord]); @@ -59,6 +52,10 @@ export const Maps = () => { setIsActive(false); }; + const handleVisibleBalloon = () => { + setVisibleBalloon(true); + }; + return ( { ]} state={{ behaviors: ['default'], - center: coords, + center: coord, controls: ['zoomControl', 'fullscreenControl', 'geolocationControl'], zoom: 15, }} @@ -87,7 +84,7 @@ export const Maps = () => { onBoundsChange={(ymaps) => getGeoLocation(ymaps)} onLoad={(ymaps) => onLoad(ymaps)} > -
          setVisibleBalloon(true)}> +
          { gridSize: 150, }} features={placemarks} - modules={['objectManager.addon.objectsBalloon', 'objectManager.addon.objectsHint']} + modules={['objectManager.addon.objectsBalloon', 'objectManager.addon.objectsHint','objectManager.Balloon']} />
          Your location
          -
          {address}
          +
          {place}
          diff --git a/src/components/elements/FindFood/balloon.css b/src/components/elements/FindFood/balloon.css index 951149c..9abbe32 100644 --- a/src/components/elements/FindFood/balloon.css +++ b/src/components/elements/FindFood/balloon.css @@ -1,10 +1,13 @@ .balloon { - background-color: aliceblue; + background-color: white; position: absolute; - top: 60%; - left: 60%; z-index: 10; max-width: 300px; + bottom: 54%; + left: 54%; + display: none; + padding: 10px 20px; + box-shadow: -5px 5px 5px #808080; } .balloon__link {} @@ -72,10 +75,6 @@ } } -.balloon { - display: none; -} - .visible { display: block; } \ No newline at end of file diff --git a/src/components/elements/FindFood/findFood.module.scss b/src/components/elements/FindFood/findFood.module.scss index 1790dd3..23aaa39 100644 --- a/src/components/elements/FindFood/findFood.module.scss +++ b/src/components/elements/FindFood/findFood.module.scss @@ -113,6 +113,10 @@ } } +.hidden { + display: none; +} + @media(min-width:1921px) { .findFoodWrapper { background-size: cover; diff --git a/src/components/elements/Header/deliverAddress.module.scss b/src/components/elements/Header/deliverAddress.module.scss index 48c5624..b788cd4 100644 --- a/src/components/elements/Header/deliverAddress.module.scss +++ b/src/components/elements/Header/deliverAddress.module.scss @@ -6,6 +6,7 @@ justify-content: stretch; align-items: center; row-gap: 5px; + max-width: 560px; &__deliver { margin-right: 12px; diff --git a/src/components/ui/TextInput/TextInput.tsx b/src/components/ui/TextInput/TextInput.tsx index 6acec96..5f475ea 100644 --- a/src/components/ui/TextInput/TextInput.tsx +++ b/src/components/ui/TextInput/TextInput.tsx @@ -45,13 +45,13 @@ export const TextInput = forwardRef { if (address) { setValue(address); - updateSearchValue(address); } }, [address]); - console.log(value); + return (
          Date: Tue, 13 Feb 2024 18:23:23 +0300 Subject: [PATCH 14/22] added address check to see if it is within the delivery area --- src/components/elements/FindFood/FindFood.tsx | 3 +- src/components/elements/FindFood/Maps.js | 75 ++++-- src/components/elements/FindFood/balloon.css | 6 +- .../elements/FindFood/deliveryZones.js | 223 ++++++++++++++++++ src/components/elements/Header/MobileMenu.tsx | 4 +- src/store/slices/location/slice.js | 8 +- src/store/slices/restaurants/slice.ts | 15 +- src/store/slices/restaurants/types.ts | 3 +- src/utils/getGeolocationCoordinates.js | 2 +- 9 files changed, 299 insertions(+), 40 deletions(-) create mode 100644 src/components/elements/FindFood/deliveryZones.js diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 66bda48..669439d 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -27,6 +27,7 @@ export const FindFood: FC = () => { const [searchValue, setSearchValue] = useState(''); const list = useSelector(locationListSelector); + console.log(list); const address = useSelector(addressSelector); const [visiblePopup, setVisiblePopup] = useState(false); @@ -36,7 +37,7 @@ export const FindFood: FC = () => { const listRest = useSelector(restaurantListSelector); const [place, setPlace] = useState('Saint Petersburg, Shpalernaya Street, 26'); - const [coord, setCoord] = useState([59.94971367493227, 30.35151817345885]); + const [coord, setCoord] = useState([30.35151817345885, 59.94971367493227]); useEffect(() => { if (listRest.length) { diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 006f476..a2c5bcf 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -1,19 +1,24 @@ /* eslint-disable max-len */ -import { Map, ObjectManager, Polygon, YMaps } from '@pbe/react-yandex-maps'; +import { Map, ObjectManager, Placemark, Polygon, YMaps } from '@pbe/react-yandex-maps'; import cn from 'classnames'; import debounce from 'lodash.debounce'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { ReactSVG } from 'react-svg'; import './balloon.css'; -import { reverseСoordinates } from './getDeliveryZone'; +import { deliveryZones } from './deliveryZones'; export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, placemarks }) => { const [maps, setMaps] = useState(null); + const [isActive, setIsActive] = useState(null); const [visibleBalloon, setVisibleBalloon] = useState(false); const [isLoaded, setIsLoaded] = useState(true); + const mapRef = useRef(null); + const placemarkRef = useRef(null); + const polygonRef = useRef(null); + const updateSearchValue = useCallback( debounce((coord) => { handleChangeCoord(coord); @@ -28,6 +33,29 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla const onLoad = (map) => { setMaps(map); }; + useEffect(() => { + if (maps && coord?.length && placemarkRef.current && mapRef.current) { + const deliveryZone = maps?.geoQuery(deliveryZones).addToMap(mapRef.current); + deliveryZone.each(function (obj) { + obj.options.set({ + fillColor: obj.properties.get('fill'), + fillOpacity: obj.properties.get('fill-opacity'), + strokeColor: obj.properties.get('stroke'), + strokeOpacity: obj.properties.get('stroke-opacity'), + strokeWidth: obj.properties.get('stroke-width'), + }); + obj.properties.set('balloonContent', obj.properties.get('description')); + }); + + const targetZone = deliveryZone.searchContaining(placemarkRef.current).get(0); + + if (targetZone) { + console.log('доставка есть'); + } else { + console.log('нет'); + } + } + }, []); useEffect(() => { if (maps && coord?.length) { @@ -60,6 +88,7 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla @@ -71,14 +100,16 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla 'control.FullscreenControl', 'geoObject.addon.balloon', 'control.GeolocationControl', + 'geoQuery', ]} state={{ behaviors: ['default'], center: coord, controls: ['zoomControl', 'fullscreenControl', 'geolocationControl'], - zoom: 15, + zoom: 9, }} className="map" + instanceRef={mapRef} onActionBegin={handleActionBegin} onActionEnd={handleActionEnd} onBoundsChange={(ymaps) => getGeoLocation(ymaps)} @@ -93,17 +124,29 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla {!isLoaded && 'd'}
          - + {/* {deliveryZones.features.map(({ geometry, id, properties }) => { + // console.log(polygon); + // if (polygon.geometry.coordinates.contains(coord)) { + // console.log(1); + // } + return ( + + ); + })} */} + +
          Your location
          diff --git a/src/components/elements/FindFood/balloon.css b/src/components/elements/FindFood/balloon.css index 9abbe32..e1beee9 100644 --- a/src/components/elements/FindFood/balloon.css +++ b/src/components/elements/FindFood/balloon.css @@ -40,8 +40,8 @@ .pointer { position: absolute; - top: 48%; - left: 50%; + top: 43.3%; + left: 48.4%; transition: all 0.3s ease; z-index: 20; cursor: pointer; @@ -56,8 +56,8 @@ } svg { - height: 30px; width: 30px; + height: 30px; } } diff --git a/src/components/elements/FindFood/deliveryZones.js b/src/components/elements/FindFood/deliveryZones.js new file mode 100644 index 0000000..421fd2b --- /dev/null +++ b/src/components/elements/FindFood/deliveryZones.js @@ -0,0 +1,223 @@ +export const deliveryZones = { + features: [ + { + geometry: { + coordinates: [ + [ + [30.317557572666534, 59.926709209445455], + [30.31853845407954, 59.908747560199146], + [30.321055173222035, 59.908938009542545], + [30.340903519932148, 59.91628143603289], + [30.34536671573281, 59.91628143603289], + [30.35171818668041, 59.915290195578685], + [30.358960151020604, 59.91422350144856], + [30.363372384850113, 59.91401608470413], + [30.369533418957324, 59.914293537976185], + [30.37464570933971, 59.91517167570822], + [30.392961632061247, 59.91834604836555], + [30.39731933906929, 59.919484054040396], + [30.398184775654276, 59.9200198692472], + [30.393667935673072, 59.922497560421434], + [30.389172553364254, 59.928723286809166], + [30.390685319248792, 59.932223396685586], + [30.396736382786283, 59.93795200760245], + [30.39802384311343, 59.94080517457784], + [30.398098944965877, 59.94271073757233], + [30.400298356358253, 59.94926632680774], + [30.399311303440733, 59.95175797069334], + [30.397208451572986, 59.953366945394], + [30.39354455405859, 59.95496238971217], + [30.38979482585575, 59.9562564456284], + [30.38710188800474, 59.956434004442656], + [30.38559189304071, 59.95623160851031], + [30.38412481342097, 59.9557063724889], + [30.37209260120287, 59.95124962471725], + [30.363305833449683, 59.94974430066047], + [30.349025752654, 59.95013177685424], + [30.345925119032987, 59.949453690528266], + [30.341064956298194, 59.948937043993276], + [30.331859614958994, 59.94685961278085], + [30.33683217855989, 59.93892322881185], + [30.335067033115635, 59.93401100208173], + [30.317557572666534, 59.926709209445455], + ], + ], + type: 'Polygon', + }, + id: 0, + properties: { + description: + '
          Стоимость доставки: 500 р.
          \nм. КУ, Маршала Новикова 1 корпус 3
          тел: +7(123)456-789
          Часы работы: с 09-00 до 02-00
          Служба доставки: +7(123)456-789', + fill: '#ed4543', + 'fill-opacity': 0.5, + stroke: '#b3b3b3', + 'stroke-opacity': 0, + 'stroke-width': '0', + }, + type: 'Feature', + }, + { + geometry: { + coordinates: [ + [ + [30.264981955459618, 59.9567962610097], + [30.199646026065437, 59.968023448258606], + [30.169090300967973, 59.95080722529315], + [30.205997497012916, 59.90936544563101], + [30.31854298727787, 59.908729629378556], + [30.31755056994245, 59.92671635778719], + [30.335070759227836, 59.93400738050088], + [30.336846381595894, 59.93891741426239], + [30.331862837246344, 59.946856988818254], + [30.293615877798302, 59.94700499658315], + [30.264981955459618, 59.9567962610097], + ], + ], + type: 'Polygon', + }, + id: 1, + properties: { + description: + '
          Стоимость доставки: 400 р.
          \nм. ОБ, Итальянская, д. 4
          тел: +7(123)456-789
          Часы работы: с 09-00 до 02-00
          Служба доставки: +7(123)456-789', + fill: '#b51eff', + 'fill-opacity': 0.5, + stroke: '#b3b3b3', + 'stroke-opacity': 0, + 'stroke-width': '0', + }, + type: 'Feature', + }, + { + geometry: { + coordinates: [ + [ + [30.331857919739864, 59.9468556967079], + [30.341084718751066, 59.948946582544735], + [30.345918059395903, 59.94945784723438], + [30.349024057435095, 59.950135933461446], + [30.363304138230347, 59.94974307563543], + [30.37208032612708, 59.95125259269921], + [30.385574850015093, 59.956232065300675], + [30.387098344735556, 59.9564392173057], + [30.389802011422514, 59.95625896822289], + [30.393567832879437, 59.95496222199212], + [30.39722100155763, 59.95336408710427], + [30.399323853425273, 59.951755112255405], + [30.400310906342714, 59.94926346813624], + [30.398111494950417, 59.94273479220603], + [30.398047121934024, 59.94079693197237], + [30.396763633197057, 59.937951021273946], + [30.390697300272862, 59.932224968122135], + [30.38918478985355, 59.92871417601343], + [30.391899185376563, 59.924987496898986], + [30.420985059934033, 59.93079274799345], + [30.435619192319283, 59.93346346737091], + [30.442485647397394, 59.93499257245471], + [30.445489721494084, 59.936392395379656], + [30.45093997021234, 59.93789983069446], + [30.4782341291479, 59.944897736946885], + [30.541319685178124, 59.94623255377958], + [30.55711253185781, 59.96241829775388], + [30.550246076779697, 59.97240128977449], + [30.522780256467193, 59.98152103953561], + [30.501150922971096, 59.98427367628628], + [30.48192484875235, 59.994422039546485], + [30.477118330197673, 60.01195930333351], + [30.46578867931872, 60.02158363824368], + [30.491194563107765, 60.06228408109402], + [30.43626292248278, 60.07927063274563], + [30.365538435178056, 60.095048248449004], + [30.26254160900617, 60.10190571826372], + [30.166067915158507, 60.0751534867464], + [29.95011790295141, 60.038934706821934], + [29.962477522092037, 60.01109985092908], + [30.011724782523764, 59.9909448999639], + [30.199672532615285, 59.968018224624764], + [30.264984322127837, 59.95679641593396], + [30.29361958557071, 59.947005151458676], + [30.331857919739864, 59.9468556967079], + ], + ], + type: 'Polygon', + }, + id: 2, + properties: { + description: + '
          Стоимость доставки: 200 р.
          \nм. ГП, Ул. Тисовая, 4
          тел. +7(123)456-789
          Часы работы: пн, вт, ср, чт, вс с 09:00 до 00:00 пт и сб с 09:00 до 02:00
          Служба доставки: +7(123)456-789', + fill: '#56db40', + 'fill-opacity': 0.5, + stroke: '#b3b3b3', + 'stroke-opacity': 0, + 'stroke-width': '0', + }, + type: 'Feature', + }, + { + geometry: { + coordinates: [ + [ + [30.39188845654023, 59.92497672537026], + [30.39366407890809, 59.92249921990695], + [30.398186283307165, 59.920018835586696], + [30.397324385562865, 59.919479579892105], + [30.392968478122633, 59.91834568641537], + [30.36956888667663, 59.91428651398849], + [30.363410534778456, 59.91400097944435], + [30.358979525485854, 59.914205702562214], + [30.345364632526156, 59.91626902534273], + [30.34091216556145, 59.916274412431086], + [30.32107454768721, 59.90891481949039], + [30.318553271213226, 59.908726228614185], + [30.20600778094817, 59.909329715580654], + [30.12131434909393, 59.86495487622657], + [30.011451067843932, 59.86426425113543], + [29.949652972140804, 59.88980779816408], + [29.79996425143767, 59.91671076431287], + [29.76563197604705, 59.93532308713607], + [29.67362147800017, 59.94152486691147], + [29.65576869479705, 59.907055805288635], + [29.679114642062665, 59.88359631539802], + [29.76700526706267, 59.86357361165538], + [29.793097796359557, 59.857357208798135], + [29.831549944797043, 59.81588472664123], + [29.90433436862518, 59.80965938322088], + [29.971625628390797, 59.82003430743956], + [30.10758143893768, 59.81519307943185], + [30.15427333346893, 59.796513159668955], + [30.272376360812665, 59.83386249918776], + [30.325934710422054, 59.80758400946622], + [30.33005458346892, 59.772282814787644], + [30.390479388156425, 59.75288582455442], + [30.494849505343932, 59.78959200546058], + [30.51132899753143, 59.85459398892704], + [30.531928362765797, 59.87047935897711], + [30.526435198703307, 59.89463814605622], + [30.540854754367267, 59.945830973760465], + [30.478026690402388, 59.944883678389736], + [30.445507588305833, 59.936383717008], + [30.44249278537309, 59.93498389368851], + [30.39188845654023, 59.92497672537026], + ], + ], + type: 'Polygon', + }, + id: 3, + properties: { + description: + '
          Стоимость доставки: 300 р.
          \nм. ШХ, Бейкер-стрит, дом 221-б
          тел. +7(987)654-321
          Часы работы: с 09:00 до 00:00
          Служба доставки: +7(987)654-321\n', + fill: '#ffd21e', + 'fill-opacity': 0.5, + stroke: '#b3b3b3', + 'stroke-opacity': 0, + 'stroke-width': '0', + }, + type: 'Feature', + }, + ], + metadata: { + creator: 'Yandex Map Constructor', + name: 'delivery', + }, + type: 'FeatureCollection', +}; + diff --git a/src/components/elements/Header/MobileMenu.tsx b/src/components/elements/Header/MobileMenu.tsx index bcd42a3..41abcbc 100644 --- a/src/components/elements/Header/MobileMenu.tsx +++ b/src/components/elements/Header/MobileMenu.tsx @@ -3,6 +3,7 @@ import { FC, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { Link, NavLink, useLocation } from 'react-router-dom'; +import { addressSelector } from '../../../store/slices/location/slice'; // import { useOutsideClick } from '../../../hooks/useOutsideClick'; import { isAuthSelector } from '../../../store/slices/user/slice'; import { CartButton } from '../../ui/buttons/CartButton'; @@ -15,6 +16,7 @@ type MobileMenuProps = { export const MobileMenu: FC = ({ handleLogOut }) => { const { pathname } = useLocation(); + const address = useSelector(addressSelector); useEffect(() => setMenuIsVisible(false), [pathname]); @@ -84,7 +86,7 @@ export const MobileMenu: FC = ({ handleLogOut }) => {
        • - +
        diff --git a/src/store/slices/location/slice.js b/src/store/slices/location/slice.js index 71dfe62..459b4e9 100644 --- a/src/store/slices/location/slice.js +++ b/src/store/slices/location/slice.js @@ -12,7 +12,7 @@ import { CustomErrors, MyAsyncThunkConfig, Restaurant, Status, getExtraReducers export const fetchData = async function ({ searchValue }, { rejectWithValue }) { try { const { data } = await axios.get( - `https://geocode-maps.yandex.ru/1.x?apikey=${process.env.REACT_APP_YANDEX_API_KEY}&geocode=${searchValue}&format=json&lang=en_RU&results=5`, + `https://geocode-maps.yandex.ru/1.x?apikey=${process.env.REACT_APP_YANDEX_API_KEY}&geocode=${searchValue}&sco=longlat&format=json&lang=en_RU&results=5`, ); if (data.length === 0) { @@ -34,7 +34,7 @@ const initialState = { list: [], location: { address: 'Saint Petersburg, Shpalernaya Street, 26', - coords: [59.94971367493227, 30.35151817345885], + coords: [30.35151817345885, 59.94971367493227], }, // placemarks: [], status: Status.LOADING, @@ -66,12 +66,12 @@ const locationSlice = createSlice({ return { geometry: { - coordinates: [latitude, longitude], + coordinates: [longitude, latitude], type: 'Point', }, id, properties: { - balloonContent: getBalloon( + balloonContentBody: getBalloon( id, name, logo_photos, diff --git a/src/store/slices/restaurants/slice.ts b/src/store/slices/restaurants/slice.ts index 7576787..0e60101 100644 --- a/src/store/slices/restaurants/slice.ts +++ b/src/store/slices/restaurants/slice.ts @@ -16,10 +16,6 @@ const initialState: RestaurantSliceState = { error: null, isLoaded: false, list: [], - location: { - address: 'Saint Petersburg, Shpalernaya Street, 26', - coords: [59.94971367493227, 30.35151817345885], - }, placemarks: [], status: Status.LOADING, }; @@ -34,9 +30,6 @@ const restaurantsSlice = createSlice({ setLoaded(state, action: PayloadAction) { state.isLoaded = action.payload; }, - setLocation(state, action) { - state.location = action.payload; - }, setPlacemarks(state) { state.placemarks = state.list.map((item) => { const { @@ -50,12 +43,12 @@ const restaurantsSlice = createSlice({ return { geometry: { - coordinates: [latitude, longitude], + coordinates: [longitude, latitude], type: 'Point', }, id, properties: { - balloonContent: getBalloon( + balloonContentBody: getBalloon( id, name, logo_photos, @@ -79,8 +72,6 @@ export const errorSelector = (state: RootStore) => state.restaurants.error; export const isLoadedSelector = (state: RootStore) => state.restaurants.isLoaded; export const statusSelector = (state: RootStore) => state.restaurants.status; export const placemarkSelector = (state: RootStore) => state.restaurants.placemarks; -export const addressSelector = (state: RootStore) => state.restaurants.location.address; -export const coordsSelector = (state: RootStore) => state.restaurants.location.coords; -export const { setLoaded, setLocation, setPlacemarks } = restaurantsSlice.actions; +export const { setLoaded, setPlacemarks } = restaurantsSlice.actions; export default restaurantsSlice.reducer; diff --git a/src/store/slices/restaurants/types.ts b/src/store/slices/restaurants/types.ts index 711a123..bb34a33 100644 --- a/src/store/slices/restaurants/types.ts +++ b/src/store/slices/restaurants/types.ts @@ -4,7 +4,6 @@ export interface RestaurantSliceState { error: ErrorType; isLoaded: boolean; list: Restaurant[]; - location: Location; placemarks: Placemark[]; status: Status; } @@ -21,7 +20,7 @@ type Geometry = { }; type Properties = { - balloonContent: string; + balloonContentBody: string; }; type Location = { diff --git a/src/utils/getGeolocationCoordinates.js b/src/utils/getGeolocationCoordinates.js index 9340ffe..9407dfd 100644 --- a/src/utils/getGeolocationCoordinates.js +++ b/src/utils/getGeolocationCoordinates.js @@ -4,7 +4,7 @@ export const getGeolocationCoordinates = (response) => { const geoObject = el?.GeoObject; return { address: geoObject?.metaDataProperty?.GeocoderMetaData?.Address?.formatted, - coords: geoObject?.Point?.pos.split(' ').reverse(), + coords: geoObject?.Point?.pos.split(' '), }; }); }; From bc537d9e5d7734a8699b3a76bb166d084cd91b1c Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Tue, 13 Feb 2024 19:40:05 +0300 Subject: [PATCH 15/22] updated --- src/components/elements/FindFood/FindFood.tsx | 1 - src/components/elements/FindFood/Maps.js | 22 +++++++++++++------ src/components/elements/FindFood/balloon.css | 2 +- .../elements/FindFood/deliveryZones.js | 8 +++---- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 669439d..32abb45 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -27,7 +27,6 @@ export const FindFood: FC = () => { const [searchValue, setSearchValue] = useState(''); const list = useSelector(locationListSelector); - console.log(list); const address = useSelector(addressSelector); const [visiblePopup, setVisiblePopup] = useState(false); diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index a2c5bcf..34bdf3d 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -5,12 +5,14 @@ import debounce from 'lodash.debounce'; import { useCallback, useEffect, useRef, useState } from 'react'; import { ReactSVG } from 'react-svg'; +import { DeliverAddress } from '../Header/DeliverAddress'; import './balloon.css'; import { deliveryZones } from './deliveryZones'; export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, placemarks }) => { const [maps, setMaps] = useState(null); - + const [status, setStatus] = useState('Delivery available'); + const [zone, setZone] = useState(null); const [isActive, setIsActive] = useState(null); const [visibleBalloon, setVisibleBalloon] = useState(false); const [isLoaded, setIsLoaded] = useState(true); @@ -33,8 +35,10 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla const onLoad = (map) => { setMaps(map); }; + useEffect(() => { - if (maps && coord?.length && placemarkRef.current && mapRef.current) { + if (maps && placemarkRef.current && mapRef.current) { + console.log(maps); const deliveryZone = maps?.geoQuery(deliveryZones).addToMap(mapRef.current); deliveryZone.each(function (obj) { obj.options.set({ @@ -46,18 +50,21 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla }); obj.properties.set('balloonContent', obj.properties.get('description')); }); + setZone(deliveryZone); + } + }, [maps]); - const targetZone = deliveryZone.searchContaining(placemarkRef.current).get(0); + useEffect(() => { + if (zone) { + const targetZone = zone.searchContaining(placemarkRef.current).get(0); if (targetZone) { - console.log('доставка есть'); + setStatus('Delivery available'); } else { - console.log('нет'); + setStatus('No delivery'); } } - }, []); - useEffect(() => { if (maps && coord?.length) { setIsLoaded(false); const resp = maps?.geocode(coord); @@ -163,6 +170,7 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla
        Your location
        {place}
        +
        {status}
        diff --git a/src/components/elements/FindFood/balloon.css b/src/components/elements/FindFood/balloon.css index e1beee9..c3f083e 100644 --- a/src/components/elements/FindFood/balloon.css +++ b/src/components/elements/FindFood/balloon.css @@ -3,7 +3,7 @@ position: absolute; z-index: 10; max-width: 300px; - bottom: 54%; + bottom: 57%; left: 54%; display: none; padding: 10px 20px; diff --git a/src/components/elements/FindFood/deliveryZones.js b/src/components/elements/FindFood/deliveryZones.js index 421fd2b..44ce0cd 100644 --- a/src/components/elements/FindFood/deliveryZones.js +++ b/src/components/elements/FindFood/deliveryZones.js @@ -49,7 +49,7 @@ export const deliveryZones = { description: '
        Стоимость доставки: 500 р.
        \nм. КУ, Маршала Новикова 1 корпус 3
        тел: +7(123)456-789
        Часы работы: с 09-00 до 02-00
        Служба доставки: +7(123)456-789', fill: '#ed4543', - 'fill-opacity': 0.5, + 'fill-opacity': 0.3, stroke: '#b3b3b3', 'stroke-opacity': 0, 'stroke-width': '0', @@ -80,7 +80,7 @@ export const deliveryZones = { description: '
        Стоимость доставки: 400 р.
        \nм. ОБ, Итальянская, д. 4
        тел: +7(123)456-789
        Часы работы: с 09-00 до 02-00
        Служба доставки: +7(123)456-789', fill: '#b51eff', - 'fill-opacity': 0.5, + 'fill-opacity': 0.3, stroke: '#b3b3b3', 'stroke-opacity': 0, 'stroke-width': '0', @@ -145,7 +145,7 @@ export const deliveryZones = { description: '
        Стоимость доставки: 200 р.
        \nм. ГП, Ул. Тисовая, 4
        тел. +7(123)456-789
        Часы работы: пн, вт, ср, чт, вс с 09:00 до 00:00 пт и сб с 09:00 до 02:00
        Служба доставки: +7(123)456-789', fill: '#56db40', - 'fill-opacity': 0.5, + 'fill-opacity': 0.3, stroke: '#b3b3b3', 'stroke-opacity': 0, 'stroke-width': '0', @@ -206,7 +206,7 @@ export const deliveryZones = { description: '
        Стоимость доставки: 300 р.
        \nм. ШХ, Бейкер-стрит, дом 221-б
        тел. +7(987)654-321
        Часы работы: с 09:00 до 00:00
        Служба доставки: +7(987)654-321\n', fill: '#ffd21e', - 'fill-opacity': 0.5, + 'fill-opacity': 0.3, stroke: '#b3b3b3', 'stroke-opacity': 0, 'stroke-width': '0', From 681ba712b5d7d045e579957bfaa2fb7786af11c8 Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Wed, 14 Feb 2024 14:25:31 +0300 Subject: [PATCH 16/22] refactored --- src/components/elements/FindFood/FindFood.tsx | 1 - src/components/elements/FindFood/Maps.js | 51 +++++-------------- 2 files changed, 14 insertions(+), 38 deletions(-) diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 32abb45..0aa3197 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -70,7 +70,6 @@ export const FindFood: FC = () => { if (searchValue) { dispatch(fetchLocation({ searchValue })); setVisiblePopup(true); - console.log(visiblePopup); } }, [searchValue]); diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 34bdf3d..4511221 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -1,5 +1,5 @@ /* eslint-disable max-len */ -import { Map, ObjectManager, Placemark, Polygon, YMaps } from '@pbe/react-yandex-maps'; +import { Map, ObjectManager, Placemark, YMaps } from '@pbe/react-yandex-maps'; import cn from 'classnames'; import debounce from 'lodash.debounce'; import { useCallback, useEffect, useRef, useState } from 'react'; @@ -11,7 +11,7 @@ import { deliveryZones } from './deliveryZones'; export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, placemarks }) => { const [maps, setMaps] = useState(null); - const [status, setStatus] = useState('Delivery available'); + const [status, setStatus] = useState(true); const [zone, setZone] = useState(null); const [isActive, setIsActive] = useState(null); const [visibleBalloon, setVisibleBalloon] = useState(false); @@ -19,7 +19,6 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla const mapRef = useRef(null); const placemarkRef = useRef(null); - const polygonRef = useRef(null); const updateSearchValue = useCallback( debounce((coord) => { @@ -34,12 +33,9 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla const onLoad = (map) => { setMaps(map); - }; - useEffect(() => { - if (maps && placemarkRef.current && mapRef.current) { - console.log(maps); - const deliveryZone = maps?.geoQuery(deliveryZones).addToMap(mapRef.current); + if (map && mapRef.current) { + const deliveryZone = map?.geoQuery(deliveryZones).addToMap(mapRef.current); deliveryZone.each(function (obj) { obj.options.set({ fillColor: obj.properties.get('fill'), @@ -50,23 +46,25 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla }); obj.properties.set('balloonContent', obj.properties.get('description')); }); + setZone(deliveryZone); } - }, [maps]); + }; useEffect(() => { - if (zone) { + if (zone && placemarkRef.current) { const targetZone = zone.searchContaining(placemarkRef.current).get(0); if (targetZone) { - setStatus('Delivery available'); + setStatus(true); } else { - setStatus('No delivery'); + setStatus(false); } } if (maps && coord?.length) { setIsLoaded(false); + const resp = maps?.geocode(coord); resp .then((res) => { @@ -83,7 +81,7 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla setIsActive(true); }; - const handleActionEnd = (e) => { + const handleActionEnd = () => { setIsActive(false); }; @@ -128,31 +126,9 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla src={`${process.env.PUBLIC_URL}/images/find-food/search-panel/location.svg`} wrapper="span" /> - {!isLoaded && 'd'} + {!isLoaded && 'pending'}
      - {/* {deliveryZones.features.map(({ geometry, id, properties }) => { - // console.log(polygon); - // if (polygon.geometry.coordinates.contains(coord)) { - // console.log(1); - // } - return ( - - ); - })} */} - +
      Your location
      {place}
      -
      {status}
      +
      {status ? 'Delivery available' : 'No delivery'}
      From 27ce48cbd2ca2d7a59dbce4d3f51d023a542d684 Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Wed, 14 Feb 2024 18:16:02 +0300 Subject: [PATCH 17/22] refactored --- public/images/find-food/preloader.svg | 1 + src/components/elements/FindFood/Maps.js | 11 ++++++++--- src/components/elements/FindFood/balloon.css | 12 +++++++++--- src/store/slices/restaurants/slice.ts | 2 +- src/store/slices/restaurants/types.ts | 2 +- 5 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 public/images/find-food/preloader.svg diff --git a/public/images/find-food/preloader.svg b/public/images/find-food/preloader.svg new file mode 100644 index 0000000..a470d0f --- /dev/null +++ b/public/images/find-food/preloader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index 4511221..e0390da 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -23,7 +23,7 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla const updateSearchValue = useCallback( debounce((coord) => { handleChangeCoord(coord); - }, 500), + }, 1000), [], ); const getGeoLocation = (e) => { @@ -126,19 +126,24 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla src={`${process.env.PUBLIC_URL}/images/find-food/search-panel/location.svg`} wrapper="span" /> - {!isLoaded && 'pending'} + {!isLoaded && ( + + )}
      Date: Thu, 15 Feb 2024 18:31:55 +0300 Subject: [PATCH 18/22] added popup for addresses with keyboard navigation logic --- src/components/elements/FindFood/FindFood.tsx | 73 ++++++++++------ src/components/elements/FindFood/Maps.js | 3 +- src/components/elements/FindFood/Popup.tsx | 85 +++++++++++++++++++ .../elements/FindFood/findFood.module.scss | 6 ++ .../elements/FindFood/popup.module.scss | 69 +++++++++++++++ 5 files changed, 207 insertions(+), 29 deletions(-) create mode 100644 src/components/elements/FindFood/Popup.tsx create mode 100644 src/components/elements/FindFood/popup.module.scss diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 0aa3197..68dc8e3 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -1,13 +1,10 @@ -import { faL, faLocationDot } from '@fortawesome/free-solid-svg-icons'; +import { faLocationDot } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Placemark } from '@pbe/react-yandex-maps'; -import cn from 'classnames'; -import { FC, useEffect, useRef, useState } from 'react'; +import { FC, KeyboardEvent, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { useAppDispatch } from '../../../store'; import { - addressSelector, fetchLocation, isLoadedSelector, locationListSelector, @@ -18,6 +15,7 @@ import { TextInput } from '../../ui/TextInput'; import { SearchButton } from '../../ui/buttons/SearchButton'; import { DeliveryMethod } from './DeliveryMethod'; import { Maps } from './Maps'; +import { Popup } from './Popup'; import style from './findFood.module.scss'; export const FindFood: FC = () => { @@ -27,10 +25,9 @@ export const FindFood: FC = () => { const [searchValue, setSearchValue] = useState(''); const list = useSelector(locationListSelector); - const address = useSelector(addressSelector); const [visiblePopup, setVisiblePopup] = useState(false); - const popupRef = useRef(null); + const popupRef = useRef(null); const isLoaded = useSelector(isLoadedSelector); const placemarks = useSelector(placemarkSelector); const listRest = useSelector(restaurantListSelector); @@ -73,6 +70,17 @@ export const FindFood: FC = () => { } }, [searchValue]); + const handleKeyDown = (event: KeyboardEvent) => { + if (popupRef.current && event.key === 'Enter') { + event.preventDefault(); + popupRef.current?.focus(); + } + }; + + const handleChangeStatus = (status: boolean) => { + setVisiblePopup(status); + }; + return (
      @@ -80,21 +88,38 @@ export const FindFood: FC = () => {

      Are you starving?

      Within a few clicks, find meals that are accessible near you

      -
      +
      - -
      - - - - +
      +
      + + + + +
      + +
      + {searchValue && ( { placemarks={placemarks} /> )} -
        - {list.map((el: any, i: any) => ( -
      • handleChangeLocation(el)}> - {' '} - {el.address} {el.coords[0]} {el.coords[1]} -
      • - ))} -
      diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.js index e0390da..e150bec 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.js @@ -23,9 +23,10 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla const updateSearchValue = useCallback( debounce((coord) => { handleChangeCoord(coord); - }, 1000), + }, 500), [], ); + const getGeoLocation = (e) => { const coord = e.get('target').getCenter(); updateSearchValue(coord); diff --git a/src/components/elements/FindFood/Popup.tsx b/src/components/elements/FindFood/Popup.tsx new file mode 100644 index 0000000..0457541 --- /dev/null +++ b/src/components/elements/FindFood/Popup.tsx @@ -0,0 +1,85 @@ +import classNames from 'classnames'; +import { KeyboardEvent, forwardRef, useEffect, useRef, useState } from 'react'; +import { CSSTransition } from 'react-transition-group'; +import { v4 as uuidv4 } from 'uuid'; + +import style from './popup.module.scss'; + +type PopupProps = { + handleChangeLocation: (el: any) => void; + handleChangeStatus: (status: boolean) => void; + isLoaded: boolean; + isOpen: boolean; + list: string[]; +}; + +export const Popup = forwardRef( + ({ handleChangeLocation, handleChangeStatus, isLoaded, isOpen, list }, ref) => { + const buttonRef = useRef(null); + const [activeIndex, setActiveIndex] = useState(-1); + + const handleListKeyDown = (event: KeyboardEvent) => { + if (event.key === 'ArrowDown') { + event.preventDefault(); + setActiveIndex((prevIndex) => { + if (list && prevIndex < list.length - 1) { + return prevIndex + 1; + } + return 0; + }); + } + + if (event.key === 'ArrowUp') { + event.preventDefault(); + + setActiveIndex((prevIndex) => { + if (list && prevIndex > 0) { + return prevIndex - 1; + } + return 0; + }); + } + + if (event.key === 'Escape') { + event.preventDefault(); + handleChangeStatus(false); + setActiveIndex(-1); + } + }; + + const handleButtonKeyDown = (event: KeyboardEvent, el: any) => { + if (event.key === 'Enter') { + event.preventDefault(); + handleChangeLocation(el); + setActiveIndex(-1); + } + }; + + useEffect(() => { + buttonRef.current?.focus(); + }, [activeIndex]); + + return ( + +
        + {list.map((el: any, index) => { + return ( +
      • handleChangeLocation(el)} + onKeyDown={(e) => handleButtonKeyDown(e, el)} + ref={index === activeIndex ? buttonRef : undefined} + tabIndex={0} + > + {el.address} +
      • + ); + })} +
      +
      + ); + }, +); diff --git a/src/components/elements/FindFood/findFood.module.scss b/src/components/elements/FindFood/findFood.module.scss index 23aaa39..7335426 100644 --- a/src/components/elements/FindFood/findFood.module.scss +++ b/src/components/elements/FindFood/findFood.module.scss @@ -9,6 +9,12 @@ } .findFood { + &__search { + position: relative; + + max-width: 856px; + } + &__title { font-size: 88px; font-weight: 700; diff --git a/src/components/elements/FindFood/popup.module.scss b/src/components/elements/FindFood/popup.module.scss new file mode 100644 index 0000000..f5a4431 --- /dev/null +++ b/src/components/elements/FindFood/popup.module.scss @@ -0,0 +1,69 @@ +@import "../../../styles/variables"; + +.popup { + position: absolute; + + z-index: 999; + + left: 24px; + top: 86px; + + background: $white-color; + border-radius: 8px; + + min-width: 199px; + width: calc(100% - 199px - 16px - 48px); + + box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, .15); + + transition: all $transition-short ease; + + padding: 10px 0; + + &:focus-visible { + outline: none; + } + + &:focus { + box-shadow: 2px 2px 2px 0 $base-color; + } + + &__item { + &__active { + color: $base-color; + + transition: all $transition-short ease; + } + + &:focus-visible { + outline: none; + } + + @include setTextStyle(400, 18px, 100%); + color: $text-color; + + padding: 10px 20px; + + transition: all $transition-short ease; + + border-bottom: 1px solid rgba(0, 0, 0, 0.19); + + cursor: pointer; + + &:hover { + color: $base-color; + + transition: all $transition-short ease; + } + + &:last-child { + border: none; + } + } +} + +@media(max-width:555px) { + .popup { + width: 100%; + } +} \ No newline at end of file From b3ec2edadcb15db56c5d47bc8e9cc66aec9c1d10 Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Fri, 16 Feb 2024 13:07:40 +0300 Subject: [PATCH 19/22] updated TextInput --- src/components/ui/TextInput/TextInput.tsx | 2 ++ src/components/ui/TextInput/textInput.module.scss | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/ui/TextInput/TextInput.tsx b/src/components/ui/TextInput/TextInput.tsx index 5f475ea..6374699 100644 --- a/src/components/ui/TextInput/TextInput.tsx +++ b/src/components/ui/TextInput/TextInput.tsx @@ -24,6 +24,7 @@ export const TextInput = forwardRef) => { if (event.key === 'Delete') { + event.preventDefault(); handleClickClear(); } }; @@ -61,6 +62,7 @@ export const TextInput = forwardRef Date: Fri, 16 Feb 2024 16:45:59 +0300 Subject: [PATCH 20/22] added keyboard navigation for FindFood component --- src/components/elements/FindFood/FindFood.tsx | 35 ++++++++++++++----- src/components/elements/FindFood/Maps.js | 5 ++- .../elements/FindFood/popup.module.scss | 5 ++- .../pages/SearchPage/SearchPanel.tsx | 6 ++-- src/components/ui/TextInput/TextInput.tsx | 17 +++++++-- .../ui/TextInput/textInput.module.scss | 2 ++ 6 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 68dc8e3..36eb896 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -20,20 +20,19 @@ import style from './findFood.module.scss'; export const FindFood: FC = () => { const searchRef = useRef(null); - const dispatch = useAppDispatch(); + const popupRef = useRef(null); - const [searchValue, setSearchValue] = useState(''); + const dispatch = useAppDispatch(); const list = useSelector(locationListSelector); - - const [visiblePopup, setVisiblePopup] = useState(false); - const popupRef = useRef(null); const isLoaded = useSelector(isLoadedSelector); const placemarks = useSelector(placemarkSelector); const listRest = useSelector(restaurantListSelector); - const [place, setPlace] = useState('Saint Petersburg, Shpalernaya Street, 26'); + const [place, setPlace] = useState(''); const [coord, setCoord] = useState([30.35151817345885, 59.94971367493227]); + const [searchValue, setSearchValue] = useState(''); + const [visiblePopup, setVisiblePopup] = useState(false); useEffect(() => { if (listRest.length) { @@ -71,16 +70,36 @@ export const FindFood: FC = () => { }, [searchValue]); const handleKeyDown = (event: KeyboardEvent) => { - if (popupRef.current && event.key === 'Enter') { + if (popupRef.current && event.key === 'ArrowDown') { event.preventDefault(); popupRef.current?.focus(); } + + if (event.key === 'Enter' && list.length) { + event.preventDefault(); + handleChangeLocation(list[0]); + } }; const handleChangeStatus = (status: boolean) => { setVisiblePopup(status); }; + useEffect(() => { + const handleOutsideClick = (e: MouseEvent) => { + if (popupRef.current?.contains(e.target as Node) || searchRef.current?.contains(e.target as Node)) { + setVisiblePopup(true); + } else { + setVisiblePopup(false); + } + return; + }; + + document.body.addEventListener('mousedown', handleOutsideClick); + + return () => document.body.removeEventListener('mousedown', handleOutsideClick); + }, []); + return (
      @@ -120,7 +139,7 @@ export const FindFood: FC = () => { />
      - {searchValue && ( + {place && ( { const coord = e.get('target').getCenter(); updateSearchValue(coord); @@ -112,7 +111,7 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla behaviors: ['default'], center: coord, controls: ['zoomControl', 'fullscreenControl', 'geolocationControl'], - zoom: 9, + zoom: 15, }} className="map" instanceRef={mapRef} diff --git a/src/components/elements/FindFood/popup.module.scss b/src/components/elements/FindFood/popup.module.scss index f5a4431..eb1271e 100644 --- a/src/components/elements/FindFood/popup.module.scss +++ b/src/components/elements/FindFood/popup.module.scss @@ -15,17 +15,16 @@ width: calc(100% - 199px - 16px - 48px); box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, .15); + border: 2px solid transparent; transition: all $transition-short ease; - padding: 10px 0; - &:focus-visible { outline: none; } &:focus { - box-shadow: 2px 2px 2px 0 $base-color; + border: 2px solid rgba(255, 116, 116, 0.5647058824); } &__item { diff --git a/src/components/pages/SearchPage/SearchPanel.tsx b/src/components/pages/SearchPage/SearchPanel.tsx index 83179f7..113ad89 100644 --- a/src/components/pages/SearchPage/SearchPanel.tsx +++ b/src/components/pages/SearchPage/SearchPanel.tsx @@ -54,9 +54,9 @@ export const SearchPanel: FC = () => { return; }; - document.body.addEventListener('click', handleOutsideClick); + document.body.addEventListener('mousedown', handleOutsideClick); - return () => document.body.removeEventListener('click', handleOutsideClick); + return () => document.body.removeEventListener('mousedown', handleOutsideClick); }, []); useEffect(() => { @@ -102,4 +102,4 @@ export const SearchPanel: FC = () => {
      ); -}; \ No newline at end of file +}; diff --git a/src/components/ui/TextInput/TextInput.tsx b/src/components/ui/TextInput/TextInput.tsx index 6374699..605c9ea 100644 --- a/src/components/ui/TextInput/TextInput.tsx +++ b/src/components/ui/TextInput/TextInput.tsx @@ -1,8 +1,17 @@ /* eslint-disable max-len */ import cn from 'classnames'; import debounce from 'lodash.debounce'; -import { KeyboardEvent, forwardRef, useCallback, useEffect, useRef, useState } from 'react'; -import { ChangeEvent, PropsWithChildren } from 'react'; +import { + ChangeEvent, + FocusEvent, + KeyboardEvent, + PropsWithChildren, + forwardRef, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; import { ReactSVG } from 'react-svg'; import style from './textInput.module.scss'; @@ -53,6 +62,10 @@ export const TextInput = forwardRef { + inputRef.current?.focus(); + }, [value]); + return (
      Date: Sat, 17 Feb 2024 18:03:58 +0300 Subject: [PATCH 21/22] added typing for map --- src/components/elements/FindFood/FindFood.tsx | 17 +-- .../elements/FindFood/{Maps.js => Maps.tsx} | 44 +++++--- src/components/elements/FindFood/Popup.tsx | 7 +- .../elements/FindFood/YandexGeocoder.js | 19 ---- .../elements/FindFood/getDeliveryZone.js | 27 ----- src/store/slices/location/slice.js | 102 ------------------ src/store/slices/location/slice.ts | 72 +++++++++++++ src/store/slices/location/types.js | 0 src/store/slices/location/types.ts | 16 +++ src/store/slices/restaurants/types.ts | 13 +-- src/store/utils/getExtraReducers.ts | 4 +- 11 files changed, 137 insertions(+), 184 deletions(-) rename src/components/elements/FindFood/{Maps.js => Maps.tsx} (77%) delete mode 100644 src/components/elements/FindFood/YandexGeocoder.js delete mode 100644 src/components/elements/FindFood/getDeliveryZone.js delete mode 100644 src/store/slices/location/slice.js create mode 100644 src/store/slices/location/slice.ts delete mode 100644 src/store/slices/location/types.js create mode 100644 src/store/slices/location/types.ts diff --git a/src/components/elements/FindFood/FindFood.tsx b/src/components/elements/FindFood/FindFood.tsx index 36eb896..0443967 100644 --- a/src/components/elements/FindFood/FindFood.tsx +++ b/src/components/elements/FindFood/FindFood.tsx @@ -2,6 +2,7 @@ import { faLocationDot } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FC, KeyboardEvent, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; import { useAppDispatch } from '../../../store'; import { @@ -10,6 +11,7 @@ import { locationListSelector, setLocation, } from '../../../store/slices/location/slice'; +import { Coords, LocationItem } from '../../../store/slices/location/types'; import { placemarkSelector, restaurantListSelector, setPlacemarks } from '../../../store/slices/restaurants/slice'; import { TextInput } from '../../ui/TextInput'; import { SearchButton } from '../../ui/buttons/SearchButton'; @@ -29,10 +31,12 @@ export const FindFood: FC = () => { const placemarks = useSelector(placemarkSelector); const listRest = useSelector(restaurantListSelector); - const [place, setPlace] = useState(''); - const [coord, setCoord] = useState([30.35151817345885, 59.94971367493227]); + const [place, setPlace] = useState(''); + const [coord, setCoord] = useState([30.35151817345885, 59.94971367493227]); const [searchValue, setSearchValue] = useState(''); - const [visiblePopup, setVisiblePopup] = useState(false); + const [visiblePopup, setVisiblePopup] = useState(false); + + const navigate = useNavigate(); useEffect(() => { if (listRest.length) { @@ -42,21 +46,22 @@ export const FindFood: FC = () => { const handleSearch = () => { dispatch(setLocation({ address: place, coords: coord })); + navigate('search'); }; const handleSearchValue = (text: string) => { setSearchValue(text); }; - const handleChangeCoord = (coord: any) => { + const handleChangeCoord = (coord: Coords) => { setCoord(coord); }; - const handleChangeAddress = (address: any) => { + const handleChangeAddress = (address: string) => { setPlace(address); }; - const handleChangeLocation = ({ address, coords }: any) => { + const handleChangeLocation = ({ address, coords }: LocationItem) => { setPlace(address); setCoord(coords); setVisiblePopup(false); diff --git a/src/components/elements/FindFood/Maps.js b/src/components/elements/FindFood/Maps.tsx similarity index 77% rename from src/components/elements/FindFood/Maps.js rename to src/components/elements/FindFood/Maps.tsx index 47b7a3c..f46da37 100644 --- a/src/components/elements/FindFood/Maps.js +++ b/src/components/elements/FindFood/Maps.tsx @@ -2,22 +2,32 @@ import { Map, ObjectManager, Placemark, YMaps } from '@pbe/react-yandex-maps'; import cn from 'classnames'; import debounce from 'lodash.debounce'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { FC, useCallback, useEffect, useRef, useState } from 'react'; import { ReactSVG } from 'react-svg'; +import { Event } from 'yandex-maps'; +import { PlacemarkType } from '../../../store/slices/restaurants/types'; import './balloon.css'; import { deliveryZones } from './deliveryZones'; -export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, placemarks }) => { - const [maps, setMaps] = useState(null); - const [status, setStatus] = useState(true); - const [zone, setZone] = useState(null); - const [isActive, setIsActive] = useState(null); - const [visibleBalloon, setVisibleBalloon] = useState(false); - const [isLoaded, setIsLoaded] = useState(true); +type MapsProps = { + coord: [number, number]; + handleChangeAddress: (address: string) => void; + handleChangeCoord: (coord: [number, number]) => void; + place: string; + placemarks: PlacemarkType[]; +}; + +export const Maps: FC = ({ coord, handleChangeAddress, handleChangeCoord, place, placemarks }) => { + const [maps, setMaps] = useState(); + const [status, setStatus] = useState(true); + const [zone, setZone] = useState(null); + const [isActive, setIsActive] = useState(false); + const [visibleBalloon, setVisibleBalloon] = useState(false); + const [isLoaded, setIsLoaded] = useState(true); - const mapRef = useRef(null); - const placemarkRef = useRef(null); + const mapRef = useRef(); + const placemarkRef = useRef(); const updateSearchValue = useCallback( debounce((coord) => { @@ -26,17 +36,17 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla [], ); - const getGeoLocation = (e) => { + const getGeoLocation = (e: Event) => { const coord = e.get('target').getCenter(); updateSearchValue(coord); }; - const onLoad = (map) => { + const onLoad = (map: any) => { setMaps(map); if (map && mapRef.current) { const deliveryZone = map?.geoQuery(deliveryZones).addToMap(mapRef.current); - deliveryZone.each(function (obj) { + deliveryZone.each(function (obj: any) { obj.options.set({ fillColor: obj.properties.get('fill'), fillOpacity: obj.properties.get('fill-opacity'), @@ -67,11 +77,11 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla const resp = maps?.geocode(coord); resp - .then((res) => { + .then((res: any) => { setIsLoaded(true); handleChangeAddress(res.geoObjects.get(0).getAddressLine()); }) - .catch((error) => { + .catch((error: any) => { console.error('The Promise is rejected!', error); }); } @@ -117,8 +127,8 @@ export const Maps = ({ coord, handleChangeAddress, handleChangeCoord, place, pla instanceRef={mapRef} onActionBegin={handleActionBegin} onActionEnd={handleActionEnd} - onBoundsChange={(ymaps) => getGeoLocation(ymaps)} - onLoad={(ymaps) => onLoad(ymaps)} + onBoundsChange={getGeoLocation} + onLoad={onLoad} >
      void; + handleChangeLocation: (el: LocationItem) => void; handleChangeStatus: (status: boolean) => void; isLoaded: boolean; isOpen: boolean; - list: string[]; + list: LocationItem[]; }; export const Popup = forwardRef( @@ -62,7 +63,7 @@ export const Popup = forwardRef( return (
        - {list.map((el: any, index) => { + {list.map((el: LocationItem, index) => { return (
      • el.reverse())]; diff --git a/src/store/slices/location/slice.js b/src/store/slices/location/slice.js deleted file mode 100644 index 459b4e9..0000000 --- a/src/store/slices/location/slice.js +++ /dev/null @@ -1,102 +0,0 @@ -import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import axios from 'axios'; - -// import { RootStore } from '../..'; -import { getBalloon } from '../../../utils/getBalloon'; -import { getGeolocationCoordinates } from '../../../utils/getGeolocationCoordinates'; -// import { fetchRestaurantsData } from '../../utils/fetchRestaurantsData'; -import { CustomErrors, MyAsyncThunkConfig, Restaurant, Status, getExtraReducers } from '../../utils/getExtraReducers'; -// import { FiltersForRestaurants } from '../../utils/getFilterForRestaurants'; -// import { RestaurantSliceState } from './types'; - -export const fetchData = async function ({ searchValue }, { rejectWithValue }) { - try { - const { data } = await axios.get( - `https://geocode-maps.yandex.ru/1.x?apikey=${process.env.REACT_APP_YANDEX_API_KEY}&geocode=${searchValue}&sco=longlat&format=json&lang=en_RU&results=5`, - ); - - if (data.length === 0) { - return rejectWithValue(CustomErrors.ERROR_NOTHING_FOUND); - } - return getGeolocationCoordinates(data); - } catch (error) { - if (error.toJSON().status === 404) { - return rejectWithValue(CustomErrors.ERROR_NOTHING_FOUND); - } - return rejectWithValue('Error: ' + error?.message); - } -}; -export const fetchLocation = createAsyncThunk('location/fetchLocation', fetchData); - -const initialState = { - error: null, - isLoaded: false, - list: [], - location: { - address: 'Saint Petersburg, Shpalernaya Street, 26', - coords: [30.35151817345885, 59.94971367493227], - }, - // placemarks: [], - status: Status.LOADING, -}; - -const locationSlice = createSlice({ - extraReducers: (builder) => getExtraReducers(builder)(fetchLocation), - - initialState, - name: 'location', - - reducers: { - setLoaded(state, action) { - state.isLoaded = action.payload; - }, - setLocation(state, action) { - state.location = action.payload; - }, - setPlacemarks(state) { - state.placemarks = state.list.map((item) => { - const { - address: { city, latitude, longitude, street_addr }, - backgroundId, - id, - logo_photos, - name, - phone_number, - } = item; - - return { - geometry: { - coordinates: [longitude, latitude], - type: 'Point', - }, - id, - properties: { - balloonContentBody: getBalloon( - id, - name, - logo_photos, - phone_number, - street_addr, - latitude, - longitude, - backgroundId, - city, - ), - }, - type: 'Feature', - }; - }); - }, - }, -}); - -export const locationListSelector = (state) => state.location.list; -export const errorSelector = (state) => state.location.error; -export const isLoadedSelector = (state) => state.location.isLoaded; -export const statusSelector = (state) => state.location.status; -export const placemarkSelector = (state) => state.location.placemarks; -export const addressSelector = (state) => state.location.location.address; -export const coordsSelector = (state) => state.location.location.coords; - -export const { setLoaded, setLocation, setPlacemarks } = locationSlice.actions; -export default locationSlice.reducer; diff --git a/src/store/slices/location/slice.ts b/src/store/slices/location/slice.ts new file mode 100644 index 0000000..b6f9529 --- /dev/null +++ b/src/store/slices/location/slice.ts @@ -0,0 +1,72 @@ +/* eslint-disable max-len */ +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import axios from 'axios'; + +import { RootStore } from '../..'; +import { getGeolocationCoordinates } from '../../../utils/getGeolocationCoordinates'; +import { CustomErrors, MyAsyncThunkConfig, Status, getExtraReducers } from '../../utils/getExtraReducers'; +import { LocationItem, LocationSliceState } from './types'; + +export const fetchData = async function ({ searchValue }: Params, { rejectWithValue }: any) { + try { + const { data } = await axios.get( + `https://geocode-maps.yandex.ru/1.x?apikey=${process.env.REACT_APP_YANDEX_API_KEY}&geocode=${searchValue}&sco=longlat&format=json&lang=en_RU&results=5`, + ); + + if (data.length === 0) { + return rejectWithValue(CustomErrors.ERROR_NOTHING_FOUND); + } + return getGeolocationCoordinates(data); + } catch (error: any) { + if (error.toJSON().status === 404) { + return rejectWithValue(CustomErrors.ERROR_NOTHING_FOUND); + } + return rejectWithValue('Error: ' + error?.message); + } +}; + +interface Params { + searchValue: string; +} + +export const fetchLocation = createAsyncThunk( + 'location/fetchLocation', + fetchData, +); + +const initialState: LocationSliceState = { + error: null, + isLoaded: false, + list: [], + location: { + address: 'Saint Petersburg, Shpalernaya Street, 26', + coords: [30.35151817345885, 59.94971367493227], + }, + status: Status.LOADING, +}; + +const locationSlice = createSlice({ + extraReducers: (builder) => getExtraReducers(builder)(fetchLocation), + + initialState, + name: 'location', + + reducers: { + setLoaded(state, action) { + state.isLoaded = action.payload; + }, + setLocation(state, action) { + state.location = action.payload; + }, + }, +}); + +export const locationListSelector = (state: RootStore) => state.location.list; +export const errorSelector = (state: RootStore) => state.location.error; +export const isLoadedSelector = (state: RootStore) => state.location.isLoaded; +export const statusSelector = (state: RootStore) => state.location.status; +export const addressSelector = (state: RootStore) => state.location.location.address; +export const coordsSelector = (state: RootStore) => state.location.location.coords; + +export const { setLoaded, setLocation } = locationSlice.actions; +export default locationSlice.reducer; diff --git a/src/store/slices/location/types.js b/src/store/slices/location/types.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/store/slices/location/types.ts b/src/store/slices/location/types.ts new file mode 100644 index 0000000..934d230 --- /dev/null +++ b/src/store/slices/location/types.ts @@ -0,0 +1,16 @@ +import { Status } from '../../utils/getExtraReducers'; + +export type Coords = [number, number]; + +export interface LocationItem { + address: string; + coords: Coords; +} + +export interface LocationSliceState { + error: null; + isLoaded: false; + list: LocationItem[]; + location: LocationItem; + status: Status.LOADING; +} diff --git a/src/store/slices/restaurants/types.ts b/src/store/slices/restaurants/types.ts index 0acbfc8..5e838e9 100644 --- a/src/store/slices/restaurants/types.ts +++ b/src/store/slices/restaurants/types.ts @@ -4,26 +4,21 @@ export interface RestaurantSliceState { error: ErrorType; isLoaded: boolean; list: Restaurant[]; - placemarks: Placemark[]; + placemarks: PlacemarkType[]; status: Status; } -interface Placemark { +export interface PlacemarkType { geometry: Geometry; id: string; properties: Properties; type: string; } -type Geometry = { +export type Geometry = { coordinates: [number, number]; type: string; }; -type Properties = { +export type Properties = { balloonContent: string; }; - -type Location = { - address: string; - coords: [number, number]; -}; diff --git a/src/store/utils/getExtraReducers.ts b/src/store/utils/getExtraReducers.ts index 9b0bebe..1e1d827 100644 --- a/src/store/utils/getExtraReducers.ts +++ b/src/store/utils/getExtraReducers.ts @@ -1,5 +1,7 @@ import type { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit'; +import { LocationItem } from '../slices/location/types'; + export enum CustomErrors { ERROR_EMPTY_REQUEST = 'Are you ready to order with the best deals?', ERROR_NOTHING_FOUND = 'Nothing was found according to request.', @@ -70,7 +72,7 @@ export interface Product { interface ProductSliceState { error: ErrorType; isLoaded: boolean; - list: Product[] | Restaurant[]; + list: LocationItem[] | Product[] | Restaurant[]; status: Status; } From b22f4d4da0830b75533915e308af1eb96638f8ef Mon Sep 17 00:00:00 2001 From: Popova Julia Date: Sat, 17 Feb 2024 18:33:46 +0300 Subject: [PATCH 22/22] added the ability to enter an address on the main page --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d46291b..993628a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "foodwagon-online-shop", - "version": "1.1.4", + "version": "1.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "foodwagon-online-shop", - "version": "1.1.4", + "version": "1.1.5", "dependencies": { "@types/jest": "^27.5.2", "@types/node": "^16.18.68", diff --git a/package.json b/package.json index a82c593..fb00df1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "foodwagon-online-shop", - "version": "1.1.4", + "version": "1.1.5", "private": true, "dependencies": { "@types/jest": "^27.5.2",