@@ -22,15 +112,47 @@ export const FindFood: FC = () => {
Are you starving?
Within a few clicks, find meals that are accessible near you
-
+
+
+
+
+
+
+
+
-
+
+ {place && (
+
+ )}
diff --git a/src/components/elements/FindFood/Maps.tsx b/src/components/elements/FindFood/Maps.tsx
new file mode 100644
index 0000000..f46da37
--- /dev/null
+++ b/src/components/elements/FindFood/Maps.tsx
@@ -0,0 +1,170 @@
+/* eslint-disable max-len */
+import { Map, ObjectManager, Placemark, YMaps } from '@pbe/react-yandex-maps';
+import cn from 'classnames';
+import debounce from 'lodash.debounce';
+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';
+
+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();
+ const placemarkRef = useRef();
+
+ const updateSearchValue = useCallback(
+ debounce((coord) => {
+ handleChangeCoord(coord);
+ }, 500),
+ [],
+ );
+
+ const getGeoLocation = (e: Event) => {
+ const coord = e.get('target').getCenter();
+ updateSearchValue(coord);
+ };
+
+ const onLoad = (map: any) => {
+ setMaps(map);
+
+ if (map && mapRef.current) {
+ const deliveryZone = map?.geoQuery(deliveryZones).addToMap(mapRef.current);
+ deliveryZone.each(function (obj: any) {
+ 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'));
+ });
+
+ setZone(deliveryZone);
+ }
+ };
+
+ useEffect(() => {
+ if (zone && placemarkRef.current) {
+ const targetZone = zone.searchContaining(placemarkRef.current).get(0);
+
+ if (targetZone) {
+ setStatus(true);
+ } else {
+ setStatus(false);
+ }
+ }
+
+ if (maps && coord?.length) {
+ setIsLoaded(false);
+
+ const resp = maps?.geocode(coord);
+ resp
+ .then((res: any) => {
+ setIsLoaded(true);
+ handleChangeAddress(res.geoObjects.get(0).getAddressLine());
+ })
+ .catch((error: any) => {
+ console.error('The Promise is rejected!', error);
+ });
+ }
+ }, [coord]);
+
+ const handleActionBegin = () => {
+ setIsActive(true);
+ };
+
+ const handleActionEnd = () => {
+ setIsActive(false);
+ };
+
+ const handleVisibleBalloon = () => {
+ setVisibleBalloon(true);
+ };
+
+ return (
+
+
+
+ );
+};
diff --git a/src/components/elements/FindFood/Popup.tsx b/src/components/elements/FindFood/Popup.tsx
new file mode 100644
index 0000000..8cb7daf
--- /dev/null
+++ b/src/components/elements/FindFood/Popup.tsx
@@ -0,0 +1,86 @@
+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 { LocationItem } from '../../../store/slices/location/types';
+import style from './popup.module.scss';
+
+type PopupProps = {
+ handleChangeLocation: (el: LocationItem) => void;
+ handleChangeStatus: (status: boolean) => void;
+ isLoaded: boolean;
+ isOpen: boolean;
+ list: LocationItem[];
+};
+
+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: LocationItem, 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/balloon.css b/src/components/elements/FindFood/balloon.css
new file mode 100644
index 0000000..035706e
--- /dev/null
+++ b/src/components/elements/FindFood/balloon.css
@@ -0,0 +1,86 @@
+.balloon {
+ background-color: white;
+ position: absolute;
+ z-index: 10;
+ max-width: 300px;
+ bottom: 57%;
+ left: 54%;
+ display: none;
+ padding: 10px 20px;
+ box-shadow: -5px 5px 5px #808080;
+}
+
+.balloon__link {}
+
+.balloon__logo {
+ width: 100px;
+ 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;
+ position: relative;
+}
+
+.pointer {
+ position: absolute;
+ top: 40.55%;
+ left: 48.4%;
+ transition: all 0.3s ease;
+ z-index: 20;
+ cursor: pointer;
+}
+
+.placemark {
+
+ transition: all 0.3s ease;
+
+ path {
+ fill: #f75900;
+ }
+
+ svg {
+ width: 32px;
+ height: 40px;
+ }
+}
+
+.pointer.active {
+
+ transition: all 0.3s ease;
+ transform: translateY(-15px);
+}
+
+.placemark.active {
+
+ path {
+ fill: #757575;
+ transition: all 0.3s ease;
+ }
+}
+
+.visible {
+ display: block;
+}
+
+.preloader {
+ position: absolute;
+ top: 4%;
+ left: 7%;
+}
\ 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..67e589c
--- /dev/null
+++ b/src/components/elements/FindFood/balloon.module.scss
@@ -0,0 +1,23 @@
+.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;
+ position: relative;
+}
+
+.pointer {
+ position: absolute;
+ left: 0;
+ top: 0;
+}
\ No newline at end of file
diff --git a/src/components/elements/FindFood/deliveryZones.js b/src/components/elements/FindFood/deliveryZones.js
new file mode 100644
index 0000000..44ce0cd
--- /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.3,
+ 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.3,
+ 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.3,
+ 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.3,
+ 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/FindFood/findFood.module.scss b/src/components/elements/FindFood/findFood.module.scss
index 25e4c07..7335426 100644
--- a/src/components/elements/FindFood/findFood.module.scss
+++ b/src/components/elements/FindFood/findFood.module.scss
@@ -4,11 +4,17 @@
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;
}
.findFood {
+ &__search {
+ position: relative;
+
+ max-width: 856px;
+ }
+
&__title {
font-size: 88px;
font-weight: 700;
@@ -113,6 +119,10 @@
}
}
+.hidden {
+ display: none;
+}
+
@media(min-width:1921px) {
.findFoodWrapper {
background-size: cover;
diff --git a/src/components/elements/FindFood/popup.module.scss b/src/components/elements/FindFood/popup.module.scss
new file mode 100644
index 0000000..eb1271e
--- /dev/null
+++ b/src/components/elements/FindFood/popup.module.scss
@@ -0,0 +1,68 @@
+@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);
+ border: 2px solid transparent;
+
+ transition: all $transition-short ease;
+
+ &:focus-visible {
+ outline: none;
+ }
+
+ &:focus {
+ border: 2px solid rgba(255, 116, 116, 0.5647058824);
+ }
+
+ &__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
diff --git a/src/components/elements/Header/DeliverAddress.tsx b/src/components/elements/Header/DeliverAddress.tsx
index b28a2de..e44176b 100644
--- a/src/components/elements/Header/DeliverAddress.tsx
+++ b/src/components/elements/Header/DeliverAddress.tsx
@@ -6,18 +6,17 @@ 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 }) => {
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..58e6c11 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/location/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..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/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/pages/SearchPage/SearchPanel.tsx b/src/components/pages/SearchPage/SearchPanel.tsx
index 03db11b..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(() => {
@@ -93,6 +93,7 @@ export const SearchPanel: FC = () => {
handleKeyDown={handleKeyDown}
handleSearchValue={handleSearchValue}
iconUrl={'/images/header/search.svg'}
+ placeholder={'Enter Your Request'}
ref={searchRef}
/>
diff --git a/src/components/ui/TextInput/TextInput.tsx b/src/components/ui/TextInput/TextInput.tsx
index ebcfc21..605c9ea 100644
--- a/src/components/ui/TextInput/TextInput.tsx
+++ b/src/components/ui/TextInput/TextInput.tsx
@@ -1,31 +1,43 @@
/* eslint-disable max-len */
import cn from 'classnames';
import debounce from 'lodash.debounce';
-import { KeyboardEvent, forwardRef, useCallback, 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';
type TextInputProps = {
+ address?: string;
classNames?: string;
handleKeyDown?: (event: KeyboardEvent) => void;
handleSearchValue: (text: string) => void;
iconUrl?: string;
+ placeholder: string;
};
export const TextInput = forwardRef>(
- ({ children, classNames, handleKeyDown, handleSearchValue, iconUrl }, ref) => {
+ ({ address, children, classNames, handleKeyDown, handleSearchValue, iconUrl, placeholder }, ref) => {
const inputRef = useRef(null);
const [value, setValue] = useState('');
const handleKeyDeleteDown = (event: KeyboardEvent) => {
if (event.key === 'Delete') {
+ event.preventDefault();
handleClickClear();
}
};
-
+
const handleClickClear = () => {
setValue('');
handleSearchValue('');
@@ -44,6 +56,16 @@ export const TextInput = forwardRef {
+ if (address) {
+ setValue(address);
+ }
+ }, [address]);
+
+ useEffect(() => {
+ inputRef.current?.focus();
+ }, [value]);
+
return (
(
+ `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.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/slice.ts b/src/store/slices/restaurants/slice.ts
index 2cb79b1..3e4b3fb 100644
--- a/src/store/slices/restaurants/slice.ts
+++ b/src/store/slices/restaurants/slice.ts
@@ -1,6 +1,7 @@
import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { RootStore } from '../..';
+import { getBalloon } from '../../../utils/getBalloon';
import { fetchRestaurantsData } from '../../utils/fetchRestaurantsData';
import { MyAsyncThunkConfig, Restaurant, Status, getExtraReducers } from '../../utils/getExtraReducers';
import { FiltersForRestaurants } from '../../utils/getFilterForRestaurants';
@@ -15,6 +16,7 @@ const initialState: RestaurantSliceState = {
error: null,
isLoaded: false,
list: [],
+ placemarks: [],
status: Status.LOADING,
};
@@ -28,6 +30,40 @@ const restaurantsSlice = createSlice({
setLoaded(state, action: PayloadAction) {
state.isLoaded = 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: {
+ balloonContent: getBalloon(
+ id,
+ name,
+ logo_photos,
+ phone_number,
+ street_addr,
+ latitude,
+ longitude,
+ backgroundId,
+ city,
+ ),
+ },
+ type: 'Feature',
+ };
+ });
+ },
},
});
@@ -35,6 +71,7 @@ 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 { setLoaded } = 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 9da809a..5e838e9 100644
--- a/src/store/slices/restaurants/types.ts
+++ b/src/store/slices/restaurants/types.ts
@@ -4,5 +4,21 @@ export interface RestaurantSliceState {
error: ErrorType;
isLoaded: boolean;
list: Restaurant[];
+ placemarks: PlacemarkType[];
status: Status;
}
+export interface PlacemarkType {
+ geometry: Geometry;
+ id: string;
+ properties: Properties;
+ type: string;
+}
+
+export type Geometry = {
+ coordinates: [number, number];
+ type: string;
+};
+
+export type Properties = {
+ balloonContent: string;
+};
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;
}
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)}
+
`;
+};
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..9407dfd
--- /dev/null
+++ b/src/utils/getGeolocationCoordinates.js
@@ -0,0 +1,10 @@
+export const getGeolocationCoordinates = (response) => {
+ // 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(' '),
+ };
+ });
+};