| @@ -45,6 +45,8 @@ | |||
| "redux-saga": "^1.1.3", | |||
| "rive-react": "^3.0.23", | |||
| "sass": "^1.34.1", | |||
| "slate": "^0.86.0", | |||
| "slate-react": "^0.86.0", | |||
| "socket.io": "^4.5.1", | |||
| "socket.io-client": "^4.5.1", | |||
| "styled-components": "^5.3.5", | |||
| @@ -14,6 +14,12 @@ | |||
| href="https://fonts.googleapis.com/css?family=Poppins&display=swap" | |||
| onload="this.onload=null;this.rel='stylesheet';this.type='text/css'" | |||
| /> | |||
| <link | |||
| rel="preload" | |||
| as="style" | |||
| href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700&display=swap" | |||
| onload="this.onload=null;this.rel='stylesheet';this.type='text/css'" | |||
| /> | |||
| <link | |||
| style | |||
| rel="preload" | |||
| @@ -20,6 +20,7 @@ import { | |||
| ADMIN_HOME_PAGE, | |||
| MESSAGES_LIST_PAGE, | |||
| DIRECT_CHAT_PAGE, | |||
| MARKETPLACE_PAGE, | |||
| } from "./constants/pages"; | |||
| import LoginPage from "./pages/LoginPage/LoginPage"; | |||
| import AdminLoginPage from "./pages/AdminLoginPage/AdminLoginPage"; | |||
| @@ -42,6 +43,7 @@ import AboutPage from "./pages/About/AboutPage"; | |||
| import AuthRoute from "./components/Router/AuthRoute"; | |||
| import AdminRoute from "./components/Router/AdminRoute"; | |||
| import AdminHomePage from "./pages/AdminHomePage/AdminHomePage"; | |||
| import MarketplacePage from "./pages/Marketplace/MarketplacePage"; | |||
| const AppRoutes = () => { | |||
| return ( | |||
| @@ -54,12 +56,17 @@ const AppRoutes = () => { | |||
| <AuthRoute path={FORGOT_PASSWORD_PAGE} component={ForgotPasswordPage} /> | |||
| <AuthRoute path={RESET_PASSWORD_PAGE} component={ResetPasswordPage} /> | |||
| <Route path={REGISTER_SUCCESSFUL_PAGE} component={RegisterSuccessful} /> | |||
| <Route path={MARKETPLACE_PAGE} component={MarketplacePage} /> | |||
| <Route path={CREATE_OFFER_PAGE} component={CreateOffer} /> | |||
| <Route path={ITEM_DETAILS_PAGE} component={ItemDetailsPage} /> | |||
| <Route path={PROFILE_PAGE} component={ProfilePage} /> | |||
| <Route path={ABOUT_PAGE} component={AboutPage} /> | |||
| <Route path={HOME_PAGE} component={HomePage} /> | |||
| <PrivateRoute exact path={MESSAGES_LIST_PAGE} component={MessagesListPage} /> | |||
| <PrivateRoute | |||
| exact | |||
| path={MESSAGES_LIST_PAGE} | |||
| component={MessagesListPage} | |||
| /> | |||
| <PrivateRoute path={DIRECT_CHAT_PAGE} component={DirectChatPage} /> | |||
| <PrivateRoute path={MY_OFFERS_PAGE} component={MyOffers} /> | |||
| <AdminRoute path={ADMIN_HOME_PAGE} component={AdminHomePage} /> | |||
| @@ -0,0 +1,8 @@ | |||
| <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> | |||
| <path d="M6 4.5H15.75" stroke="#667080" stroke-width="1.24" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M6 9H15.75" stroke="#667080" stroke-width="1.24" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M6 13.5H15.75" stroke="#667080" stroke-width="1.24" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M2.25 4.5H2.2575" stroke="#667080" stroke-width="1.24" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M2.25 9H2.2575" stroke="#667080" stroke-width="1.24" stroke-linecap="round" stroke-linejoin="round"/> | |||
| <path d="M2.25 13.5H2.2575" stroke="#667080" stroke-width="1.24" stroke-linecap="round" stroke-linejoin="round"/> | |||
| </svg> | |||
| @@ -0,0 +1,95 @@ | |||
| import React, { useEffect, useRef } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useLocation } from "react-router-dom"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectAboutRouteSelected } from "../../store/selectors/appSelectors"; | |||
| import scrollConstants from "../../constants/scrollConstants"; | |||
| import { setAboutRouteSelected } from "../../store/actions/app/appActions"; | |||
| import { AboutPageContainer } from "./AboutPageContent.styled"; | |||
| import AboutComponent from "./AboutComponent"; | |||
| import PricesComponent from "../Prices/PricesComponent"; | |||
| import PrivacyPolicyComponent from "../PrivacyPolicy/PrivacyPolicyComponent"; | |||
| import AboutFooter from "../Footer/AboutFooter"; | |||
| const AboutPageContent = () => { | |||
| const aboutRef = useRef(null); | |||
| const pricesRef = useRef(null); | |||
| const privacyPolicyRef = useRef(null); | |||
| const dispatch = useDispatch(); | |||
| const aboutRouteSelected = useSelector(selectAboutRouteSelected); | |||
| const location = useLocation(); | |||
| useEffect(() => { | |||
| if (location.state && location.state?.clicked) { | |||
| if (location.state.navigation === scrollConstants.about.aboutPage) { | |||
| window.scrollTo({ top: 0, behavior: "smooth" }); | |||
| if (aboutRouteSelected !== scrollConstants.about.aboutPage) { | |||
| dispatch(setAboutRouteSelected(scrollConstants.about.aboutPage)); | |||
| } | |||
| } | |||
| if (location.state.navigation === scrollConstants.about.pricesPage) { | |||
| const yAxis = pricesRef.current.offsetTop; | |||
| window.scrollTo({ top: yAxis, behavior: "smooth" }); | |||
| if (aboutRouteSelected !== scrollConstants.about.pricesPage) { | |||
| dispatch(setAboutRouteSelected(scrollConstants.about.pricesPage)); | |||
| } | |||
| } | |||
| if ( | |||
| location.state.navigation === scrollConstants.about.privacyPolicyPage | |||
| ) { | |||
| const yAxis = privacyPolicyRef.current.offsetTop - 64; | |||
| window.scrollTo({ top: yAxis, behavior: "smooth" }); | |||
| if (aboutRouteSelected !== scrollConstants.about.privacyPolicyPage) { | |||
| dispatch( | |||
| setAboutRouteSelected(scrollConstants.about.privacyPolicyPage) | |||
| ); | |||
| } | |||
| } | |||
| location.state = {}; | |||
| } | |||
| }, [location]); | |||
| useEffect(() => { | |||
| const listener = () => { | |||
| if ( | |||
| window.scrollY > | |||
| pricesRef.current.offsetTop - window.innerHeight / 2 | |||
| ) { | |||
| if ( | |||
| window.scrollY > | |||
| privacyPolicyRef.current.offsetTop - window.innerHeight / 2 | |||
| ) { | |||
| if (aboutRouteSelected !== scrollConstants.about.privacyPolicyPage) { | |||
| dispatch( | |||
| setAboutRouteSelected(scrollConstants.about.privacyPolicyPage) | |||
| ); | |||
| } | |||
| } else if (aboutRouteSelected !== scrollConstants.about.pricesPage) { | |||
| dispatch(setAboutRouteSelected(scrollConstants.about.pricesPage)); | |||
| } | |||
| } else { | |||
| if (aboutRouteSelected !== scrollConstants.about.aboutPage) { | |||
| dispatch(setAboutRouteSelected(scrollConstants.about.aboutPage)); | |||
| } | |||
| } | |||
| }; | |||
| window.addEventListener("scroll", listener); | |||
| return () => window.removeEventListener("scroll", listener); | |||
| }, [aboutRouteSelected]); | |||
| return ( | |||
| <AboutPageContainer> | |||
| <AboutComponent ref={aboutRef} id={scrollConstants.about.aboutPage} /> | |||
| <PricesComponent ref={pricesRef} id={scrollConstants.about.pricesPage} /> | |||
| <PrivacyPolicyComponent | |||
| ref={privacyPolicyRef} | |||
| id={scrollConstants.about.privacyPolicyPage} | |||
| /> | |||
| <AboutFooter /> | |||
| </AboutPageContainer> | |||
| ); | |||
| }; | |||
| AboutPageContent.propTypes = { | |||
| children: PropTypes.node, | |||
| }; | |||
| export default AboutPageContent; | |||
| @@ -0,0 +1,12 @@ | |||
| import { Box } from "@mui/system"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../themes"; | |||
| export const AboutPageContainer = styled(Box)` | |||
| margin-top: 64px; | |||
| background-color: ${selectedTheme.colors.staticBackgroundColor}; | |||
| position: relative; | |||
| @media (max-width: 600px) { | |||
| margin-top: 53px; | |||
| } | |||
| ` | |||
| @@ -3,13 +3,13 @@ import PropTypes from "prop-types"; | |||
| import { CheckOffersButtonContainer } from "./CheckOffersButton.styled"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { HOME_PAGE } from "../../../constants/pages"; | |||
| import { MARKETPLACE_PAGE } from "../../../constants/pages"; | |||
| import history from "../../../store/utils/history"; | |||
| const CheckOffersButton = () => { | |||
| const { t } = useTranslation(); | |||
| const handleClick = () => { | |||
| history.push(HOME_PAGE); | |||
| history.push(MARKETPLACE_PAGE); | |||
| }; | |||
| return ( | |||
| <CheckOffersButtonContainer | |||
| @@ -231,8 +231,8 @@ export const NextButtonContainer = styled(PrimaryButton)` | |||
| } | |||
| @media screen and (max-width: 600px) { | |||
| position: absolute; | |||
| bottom: 18px; | |||
| /* position: absolute; */ | |||
| /* bottom: 18px; */ | |||
| height: 44px; | |||
| width: 339px; | |||
| /* left: 9px; */ | |||
| @@ -1,45 +1,22 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import useIsMobile from "../../../../../hooks/useIsMobile"; | |||
| import { FieldLabel } from "../FirstPartCreateOffer.styled"; | |||
| import { DescriptionField } from "./OfferDescriptionField.styled"; | |||
| import RichTextComponent from "../../../../RichTextComponent/RichTextComponent"; | |||
| const OfferDescriptionField = (props) => { | |||
| const formik = props.formik; | |||
| const { t } = useTranslation(); | |||
| const { isMobile } = useIsMobile(); | |||
| const handleChange = (newValue) => { | |||
| formik.setFieldValue("description", newValue); | |||
| }; | |||
| return ( | |||
| <> | |||
| <FieldLabel leftText={t("offer.productDescription")} /> | |||
| {!isMobile ? ( | |||
| <DescriptionField | |||
| name="description" | |||
| placeholder={t("offer.description")} | |||
| margin="normal" | |||
| italicPlaceholder | |||
| value={formik.values.description} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.description && formik.errors.description} | |||
| helperText={formik.touched.description && formik.errors.description} | |||
| fullWidth | |||
| multiline | |||
| minRows={4} | |||
| height={"100px"} | |||
| /> | |||
| ) : ( | |||
| <DescriptionField | |||
| name="description" | |||
| placeholder={t("offer.description")} | |||
| margin="normal" | |||
| italicPlaceholder | |||
| value={formik.values.description} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.description && formik.errors.description} | |||
| helperText={formik.touched.description && formik.errors.description} | |||
| fullWidth | |||
| /> | |||
| )} | |||
| <RichTextComponent | |||
| onChange={handleChange} | |||
| value={formik.values.description} | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../../../themes"; | |||
| import { TextField } from "../../../../TextFields/TextField/TextField"; | |||
| export const DescriptionField = styled(TextField)` | |||
| @@ -13,3 +14,17 @@ export const DescriptionField = styled(TextField)` | |||
| } | |||
| } | |||
| `; | |||
| export const Element = styled.p` | |||
| background-color: ${selectedTheme.colors.primaryBackgroundColor}; | |||
| padding: 0; | |||
| box-sizing: border-box; | |||
| overflow-y: auto; | |||
| width: 100%; | |||
| margin: 16px 0; | |||
| padding-left: 0; | |||
| height: 100px; | |||
| font-size: 16px; | |||
| padding: 8px 14px; | |||
| border: 1px solid rgba(0, 0, 0, 0.23); | |||
| border-radius: 4px; | |||
| `; | |||
| @@ -2,6 +2,9 @@ import styled from "styled-components"; | |||
| import { TextField } from "../../../../TextFields/TextField/TextField"; | |||
| export const TitleField = styled(TextField)` | |||
| & input { | |||
| background-color: white; | |||
| } | |||
| @media (max-width: 600px) { | |||
| margin-bottom: 0; | |||
| & div div input { | |||
| @@ -11,4 +14,4 @@ export const TitleField = styled(TextField)` | |||
| height: 40px; | |||
| } | |||
| } | |||
| `; | |||
| `; | |||
| @@ -0,0 +1,25 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { EditableContainer } from "./OfferDescription.styled"; | |||
| import RichTextComponent from "../../../../RichTextComponent/RichTextComponent"; | |||
| import { isJsonString } from "../../../../../util/helpers/jsonHelper"; | |||
| import { useMemo } from "react"; | |||
| const OfferDescription = (props) => { | |||
| const description = useMemo( | |||
| () => | |||
| isJsonString(props?.value) ? ( | |||
| <RichTextComponent itemDetails value={props?.value || ""} readOnly /> | |||
| ) : ( | |||
| <></> | |||
| ), | |||
| [props?.value] | |||
| ); | |||
| return <EditableContainer>{description}</EditableContainer>; | |||
| }; | |||
| OfferDescription.propTypes = { | |||
| value: PropTypes.string, | |||
| }; | |||
| export default OfferDescription; | |||
| @@ -0,0 +1,13 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const EditableContainer = styled(Box)` | |||
| font-size: 16px; | |||
| line-height: 22px; | |||
| white-space: pre-line; | |||
| max-width: 100%; | |||
| @media (max-width: 600px) { | |||
| font-size: 14px; | |||
| max-width: 100%; | |||
| } | |||
| `; | |||
| @@ -2,7 +2,6 @@ import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| Details, | |||
| OfferDescriptionText, | |||
| OfferDescriptionTitle, | |||
| OfferImage, | |||
| OfferLittleDetails, | |||
| @@ -19,6 +18,7 @@ import { getImageUrl, variants } from "../../../../util/helpers/imageUrlGetter"; | |||
| import { useEffect } from "react"; | |||
| import { useState } from "react"; | |||
| import ImagesCarousel from "../ImagesCarousel/ImagesCarousel"; | |||
| import OfferDescription from "./OfferDescription/OfferDescription"; | |||
| const OfferDetails = (props) => { | |||
| const offer = props.offer; | |||
| @@ -110,9 +110,7 @@ const OfferDetails = (props) => { | |||
| <OfferDescriptionTitle> | |||
| {t("itemDetailsCard.description")} | |||
| </OfferDescriptionTitle> | |||
| <OfferDescriptionText showBarterButton={props.showExchangeButton}> | |||
| {offer?.description} | |||
| </OfferDescriptionText> | |||
| <OfferDescription value={offer?.description} /> | |||
| <DesciprtionPostDate previewCard={props.previewCard}> | |||
| {date} | |||
| </DesciprtionPostDate> | |||
| @@ -135,17 +135,10 @@ export const OfferDescriptionText = styled(Box)` | |||
| line-height: 22px; | |||
| white-space: pre-line; | |||
| max-width: 100%; | |||
| /* max-width: ${(props) => | |||
| props.showBarterButton ? "calc(100% - 230px)" : "100%"}; */ | |||
| @media (max-width: 600px) { | |||
| font-size: 14px; | |||
| max-width: 100%; | |||
| } | |||
| /* max-width: calc(100% - 230px); */ | |||
| /* overflow: hidden; */ | |||
| /* display: -webkit-box; | |||
| -webkit-line-clamp: 5; | |||
| -webkit-box-orient: vertical; */ | |||
| `; | |||
| export const DesciprtionPostDate = styled(Typography)` | |||
| display: none; | |||
| @@ -6,6 +6,8 @@ import { | |||
| OfferDescriptionTitle, | |||
| } from "./OfferDescription.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import RichTextComponent from "../../../RichTextComponent/RichTextComponent"; | |||
| import { isJsonString } from "../../../../util/helpers/jsonHelper"; | |||
| const OfferDescription = (props) => { | |||
| const { t } = useTranslation(); | |||
| @@ -14,7 +16,13 @@ const OfferDescription = (props) => { | |||
| <OfferDescriptionTitle> | |||
| {t("offer.descriptionLabel")} | |||
| </OfferDescriptionTitle> | |||
| <OfferDescriptionText>{props.description}</OfferDescriptionText> | |||
| <OfferDescriptionText> | |||
| {isJsonString(props?.description) ? ( | |||
| <RichTextComponent readOnly offerCard value={props?.description} /> | |||
| ) : ( | |||
| "" | |||
| )} | |||
| </OfferDescriptionText> | |||
| </OfferDescriptionContainer> | |||
| ); | |||
| }; | |||
| @@ -67,5 +67,5 @@ export const LinkText = styled(Typography)` | |||
| `; | |||
| export const Arrow = styled(ArrowButton)` | |||
| transform: rotate(-45deg); | |||
| transform: rotate(-90deg); | |||
| `; | |||
| @@ -21,6 +21,7 @@ import { | |||
| ADMIN_HOME_PAGE, | |||
| BASE_PAGE, | |||
| HOME_PAGE, | |||
| MARKETPLACE_PAGE, | |||
| } from "../../constants/pages"; | |||
| import { fetchMineProfile } from "../../store/actions/profile/profileActions"; | |||
| import useSearch from "../../hooks/useOffers/useSearch"; | |||
| @@ -106,12 +107,21 @@ const Header = () => { | |||
| }, | |||
| }); | |||
| } else { | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| logo: true, | |||
| }, | |||
| }); | |||
| if (user) { | |||
| history.push({ | |||
| pathname: HOME_PAGE, | |||
| state: { | |||
| logo: true, | |||
| }, | |||
| }); | |||
| } else { | |||
| history.push({ | |||
| pathname: MARKETPLACE_PAGE, | |||
| state: { | |||
| logo: true, | |||
| }, | |||
| }); | |||
| } | |||
| if (searchRef?.current) searchRef.current.value = ""; | |||
| } | |||
| }; | |||
| @@ -143,7 +153,11 @@ const Header = () => { | |||
| handleSearch={handleSearch} | |||
| user={user} | |||
| /> | |||
| {routeMatches(ABOUT_PAGE) && <AboutHeader />} | |||
| {(routeMatches(ABOUT_PAGE) || | |||
| (!user && | |||
| (routeMatches(HOME_PAGE) || routeMatches(BASE_PAGE)))) && ( | |||
| <AboutHeader /> | |||
| )} | |||
| {user ? ( | |||
| <ToolsButtonsContainer | |||
| @@ -177,7 +191,9 @@ const Header = () => { | |||
| /> | |||
| ) : ( | |||
| <React.Fragment> | |||
| {routeMatches(ABOUT_PAGE) ? ( | |||
| {routeMatches(ABOUT_PAGE) || | |||
| routeMatches(HOME_PAGE) || | |||
| routeMatches(BASE_PAGE) ? ( | |||
| <MarketplaceLinkRouteContainer> | |||
| <MarketplaceLinkRoute onClick={() => handleLogoClick()}> | |||
| {t("admin.navigation.marketplace")} | |||
| @@ -11,16 +11,18 @@ import { useTranslation } from "react-i18next"; | |||
| import { routeMatches } from "../../../util/helpers/routeHelpers"; | |||
| import { ABOUT_PAGE, BASE_PAGE, HOME_PAGE } from "../../../constants/pages"; | |||
| import { debounceHelper } from "../../../util/helpers/debounceHelper"; | |||
| import useIsLoggedIn from "../../../hooks/useIsLoggedIn"; | |||
| const SearchInput = forwardRef((props, ref) => { | |||
| const { t } = useTranslation(); | |||
| const { isLoggedIn } = useIsLoggedIn(); | |||
| const handleSearch = () => { | |||
| if (routeMatches(HOME_PAGE) || routeMatches(BASE_PAGE)) { | |||
| console.log("uslo unutra") | |||
| console.log("uslo unutra"); | |||
| debounceHelper(() => props.handleSearch(ref.current.value), 500); | |||
| } | |||
| }; | |||
| console.log(routeMatches(HOME_PAGE)) | |||
| console.log(routeMatches(HOME_PAGE)); | |||
| const handleManualSearch = () => { | |||
| debounceHelper(() => {}, 500); | |||
| props.handleSearch(ref.current.value); | |||
| @@ -40,7 +42,10 @@ const SearchInput = forwardRef((props, ref) => { | |||
| const handleBlurSearch = () => { | |||
| ref.current.removeEventListener("keyup", listener); | |||
| }; | |||
| if (routeMatches(ABOUT_PAGE)) { | |||
| if ( | |||
| routeMatches(ABOUT_PAGE) || | |||
| (!isLoggedIn && (routeMatches(HOME_PAGE) || routeMatches(BASE_PAGE))) | |||
| ) { | |||
| return <></>; | |||
| } | |||
| return ( | |||
| @@ -0,0 +1,54 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| ColorPickerContainer, | |||
| ColorPickerLabel, | |||
| ColorPickerName, | |||
| ColorPickerNameContainer, | |||
| ColorsContainer, | |||
| } from "./ColorPicker.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import selectedTheme from "../../../themes"; | |||
| import ColorIcon from "../../RichTextComponent/ColorIcon/ColorIcon"; | |||
| const ColorPicker = (props) => { | |||
| const { t } = useTranslation(); | |||
| const handleClickColor = (singleColor) => { | |||
| props?.handleClickColor({ | |||
| color: selectedTheme.colors.colorPicker[singleColor], | |||
| colorName: singleColor, | |||
| }); | |||
| }; | |||
| return ( | |||
| <ColorPickerContainer> | |||
| <ColorPickerNameContainer> | |||
| <ColorPickerLabel>{t("colorPicker.label")}</ColorPickerLabel> | |||
| <ColorPickerName> | |||
| {t(`colorPicker.${props.selectedColorName}`)} | |||
| </ColorPickerName> | |||
| </ColorPickerNameContainer> | |||
| <ColorsContainer> | |||
| {Object.keys(selectedTheme.colors.colorPicker).map((singleColor) => { | |||
| if (singleColor === "border") return; | |||
| return ( | |||
| <ColorIcon | |||
| selected={singleColor === props?.selectedColorName} | |||
| selectedInPopover={singleColor === props?.selectedColorName} | |||
| color={selectedTheme.colors.colorPicker[singleColor]} | |||
| key={singleColor} | |||
| onClick={() => handleClickColor(singleColor)} | |||
| /> | |||
| ); | |||
| })} | |||
| </ColorsContainer> | |||
| </ColorPickerContainer> | |||
| ); | |||
| }; | |||
| ColorPicker.propTypes = { | |||
| selectedColorName: PropTypes.string, | |||
| selectedColor: PropTypes.string, | |||
| handleClickColor: PropTypes.func, | |||
| }; | |||
| export default ColorPicker; | |||
| @@ -0,0 +1,43 @@ | |||
| import { Box, Typography } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const ColorPickerContainer = styled(Box)` | |||
| padding: 9px 18px; | |||
| isolation: isolate; | |||
| width: 178px; | |||
| height: 74px; | |||
| background: white; | |||
| box-shadow: 4px 4px 9px rgba(0, 0, 0, 0.12); | |||
| border-radius: 4px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 9px; | |||
| z-index: 1000; | |||
| `; | |||
| export const ColorPickerNameContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| gap: 3px; | |||
| align-items: flex-end; | |||
| `; | |||
| export const ColorPickerLabel = styled(Typography)` | |||
| color: ${selectedTheme.colors.primaryTextDisabled}; | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| font-size: 12px; | |||
| line-height: 15.5px; | |||
| letter-spacing: 1px; | |||
| `; | |||
| export const ColorPickerName = styled(Typography)` | |||
| color: ${selectedTheme.colors.primaryText}; | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| font-size: 16px; | |||
| line-height: 18.5px; | |||
| `; | |||
| export const ColorsContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: row; | |||
| gap: 14px; | |||
| padding: 4px; | |||
| align-items: flex-end; | |||
| `; | |||
| @@ -0,0 +1,79 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useSlate } from "slate-react"; | |||
| import { Editor, Transforms, Element as SlateElement } from "slate"; | |||
| import { BlockButtonContainer, BlockButtonIcon } from "./BlockButton.styled"; | |||
| const LIST_TYPES = ["numbered-list", "bulleted-list", "link"]; | |||
| const TEXT_ALIGN_TYPES = ["left", "center", "right", "justify"]; | |||
| const BlockButton = ({ format, icon }) => { | |||
| const editor = useSlate(); | |||
| return ( | |||
| <BlockButtonContainer | |||
| active={isBlockActive( | |||
| editor, | |||
| format, | |||
| TEXT_ALIGN_TYPES.includes(format) ? "align" : "type" | |||
| )} | |||
| onMouseDown={(event) => { | |||
| event.preventDefault(); | |||
| toggleBlock(editor, format); | |||
| }} | |||
| > | |||
| <BlockButtonIcon>{icon}</BlockButtonIcon> | |||
| </BlockButtonContainer> | |||
| ); | |||
| }; | |||
| const isBlockActive = (editor, format, blockType = "type") => { | |||
| const { selection } = editor; | |||
| if (!selection) return false; | |||
| const [match] = Array.from( | |||
| Editor.nodes(editor, { | |||
| at: Editor.unhangRange(editor, selection), | |||
| match: (n) => | |||
| !Editor.isEditor(n) && | |||
| SlateElement.isElement(n) && | |||
| n[blockType] === format, | |||
| }) | |||
| ); | |||
| return !!match; | |||
| }; | |||
| const toggleBlock = (editor, format) => { | |||
| const isActive = isBlockActive( | |||
| editor, | |||
| format, | |||
| TEXT_ALIGN_TYPES.includes(format) ? "align" : "type" | |||
| ); | |||
| const isList = LIST_TYPES.includes(format); | |||
| Transforms.unwrapNodes(editor, { | |||
| match: (n) => | |||
| !Editor.isEditor(n) && | |||
| SlateElement.isElement(n) && | |||
| LIST_TYPES.includes(n.type) && | |||
| !TEXT_ALIGN_TYPES.includes(format), | |||
| split: true, | |||
| }); | |||
| let newProperties; | |||
| if (TEXT_ALIGN_TYPES.includes(format)) { | |||
| newProperties = { | |||
| align: isActive ? undefined : format, | |||
| }; | |||
| } else { | |||
| newProperties = { | |||
| type: isActive ? "paragraph" : isList ? "list-item" : format, | |||
| }; | |||
| } | |||
| Transforms.setNodes < SlateElement > (editor, newProperties); | |||
| if (!isActive && isList) { | |||
| const block = { type: format, children: [] }; | |||
| Transforms.wrapNodes(editor, block); | |||
| } | |||
| }; | |||
| BlockButton.propTypes = { | |||
| format: PropTypes.any, | |||
| icon: PropTypes.any, | |||
| }; | |||
| export default BlockButton; | |||
| @@ -0,0 +1,27 @@ | |||
| import { Box } from "@mui/system"; | |||
| import styled, { css } from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const BlockButtonContainer = styled(Box)` | |||
| display: inline; | |||
| cursor: pointer; | |||
| `; | |||
| export const BlockButtonIcon = styled(Box)` | |||
| font-size: 16px; | |||
| font-family: "Source Code Pro"; | |||
| ${(props) => | |||
| props.format === "bold" | |||
| ? `font-weight: bold;` | |||
| : props.format === "italic" | |||
| ? "font-style: italic;" | |||
| : props.format === "underline" | |||
| ? `text-decoration: underline;` | |||
| : ""} | |||
| color: ${selectedTheme.colors.primaryGrayText}; | |||
| line-height: 20px; | |||
| ${(props) => | |||
| props.active && | |||
| css` | |||
| color: ${selectedTheme.colors.primaryText}; | |||
| `} | |||
| `; | |||
| @@ -0,0 +1,58 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useSlate } from "slate-react"; | |||
| import { Editor } from "slate"; | |||
| import { ColorButtonContainer, ColorButtonIcon } from "./ColorButton.styled"; | |||
| import { useState } from "react"; | |||
| import ColorIcon from "../ColorIcon/ColorIcon"; | |||
| import ColorPicker from "../../Popovers/ColorPicker/ColorPicker"; | |||
| // const colorMarks = ["darkGray", "gray", "purple", "pink", "yellow"]; | |||
| const toggleMark = (editor, format, newColor) => { | |||
| Editor.addMark(editor, format, newColor); | |||
| }; | |||
| // const isMarkActive = (editor, format) => { | |||
| // const marks = Editor.marks(editor); | |||
| // return marks ? marks[format] === true : false; | |||
| // }; | |||
| const ColorButton = (props) => { | |||
| const editor = useSlate(); | |||
| const [isColorPickerPopoverShowing, setIsColorPickerPopoverShowing] = | |||
| useState(false); | |||
| const callbackFunction = (newColor) => { | |||
| setIsColorPickerPopoverShowing(false); | |||
| props?.setSelectedColor(newColor); | |||
| toggleMark(editor, "color", newColor.color); | |||
| }; | |||
| console.log(); | |||
| return ( | |||
| <> | |||
| <ColorButtonContainer | |||
| format={props?.format} | |||
| onMouseDown={() => setIsColorPickerPopoverShowing(true)} | |||
| > | |||
| <ColorButtonIcon format={props?.format}> | |||
| <ColorIcon selected color={props?.selectedColor.color} /> | |||
| </ColorButtonIcon> | |||
| </ColorButtonContainer> | |||
| {isColorPickerPopoverShowing && ( | |||
| <ColorPicker | |||
| selectedColor={props?.selectedColor.color} | |||
| selectedColorName={props?.selectedColor.colorName} | |||
| handleClickColor={callbackFunction} | |||
| /> | |||
| )} | |||
| </> | |||
| ); | |||
| }; | |||
| ColorButton.propTypes = { | |||
| format: PropTypes.any, | |||
| icon: PropTypes.any, | |||
| selectedColor: PropTypes.any, | |||
| setSelectedColor: PropTypes.func, | |||
| }; | |||
| export default ColorButton; | |||
| @@ -0,0 +1,29 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled, { css } from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const ColorButtonContainer = styled(Box)` | |||
| display: inline; | |||
| cursor: pointer; | |||
| position: relative; | |||
| top: 2px; | |||
| `; | |||
| export const ColorButtonIcon = styled(Box)` | |||
| font-size: 16px; | |||
| font-family: "Source Code Pro"; | |||
| ${(props) => | |||
| props.format === "bold" | |||
| ? `font-weight: bold;` | |||
| : props.format === "italic" | |||
| ? "font-style: italic;" | |||
| : props.format === "underline" | |||
| ? `text-decoration: underline;` | |||
| : ""} | |||
| color: ${selectedTheme.colors.primaryGrayText}; | |||
| line-height: 20px; | |||
| ${(props) => | |||
| props.active && | |||
| css` | |||
| color: ${selectedTheme.colors.primaryText}; | |||
| `} | |||
| `; | |||
| @@ -0,0 +1,26 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { ColorIconContainer } from "./ColorIcon.styled"; | |||
| const ColorIcon = (props) => { | |||
| return ( | |||
| <ColorIconContainer | |||
| selected={props?.selected} | |||
| backgroundColor={props?.color} | |||
| aboveEditor={props?.aboveEditor} | |||
| selectedInPopover={props?.selectedInPopover} | |||
| onClick={props?.onClick} | |||
| /> | |||
| ); | |||
| }; | |||
| ColorIcon.propTypes = { | |||
| children: PropTypes.node, | |||
| color: PropTypes.string, | |||
| selected: PropTypes.bool, | |||
| aboveEditor: PropTypes.bool, | |||
| selectedInPopover: PropTypes.bool, | |||
| onClick: PropTypes.func, | |||
| }; | |||
| export default ColorIcon; | |||
| @@ -0,0 +1,15 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const ColorIconContainer = styled(Box)` | |||
| cursor: pointer; | |||
| width: ${(props) => | |||
| props.aboveEditor ? "13.5px" : props.selectedInPopover ? "17px" : "15px"}; | |||
| height: ${(props) => | |||
| props.aboveEditor ? "13.5px" : props.selectedInPopover ? "17px" : "15px"}; | |||
| background-color: ${(props) => props.backgroundColor}; | |||
| border: 1.24px solid | |||
| ${(props) => | |||
| props.selected ? selectedTheme.colors.colorPicker.border : "transparent"}; | |||
| `; | |||
| @@ -0,0 +1,43 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { useSlate } from "slate-react"; | |||
| import { Editor } from "slate"; | |||
| import { MarkButtonContainer, MarkButtonIcon } from "./MarkButton.styled"; | |||
| const toggleMark = (editor, format) => { | |||
| const isActive = isMarkActive(editor, format); | |||
| if (isActive) { | |||
| Editor.removeMark(editor, format); | |||
| } else { | |||
| Editor.addMark(editor, format, true); | |||
| } | |||
| }; | |||
| const isMarkActive = (editor, format) => { | |||
| const marks = Editor.marks(editor); | |||
| return marks ? marks[format] === true : false; | |||
| }; | |||
| const MarkButton = ({ format, icon }) => { | |||
| const editor = useSlate(); | |||
| return ( | |||
| <MarkButtonContainer | |||
| active={isMarkActive(editor, format)} | |||
| format={format} | |||
| onMouseDown={(event) => { | |||
| event.preventDefault(); | |||
| toggleMark(editor, format); | |||
| }} | |||
| > | |||
| <MarkButtonIcon format={format} active={isMarkActive(editor, format)}> | |||
| {icon} | |||
| </MarkButtonIcon> | |||
| </MarkButtonContainer> | |||
| ); | |||
| }; | |||
| MarkButton.propTypes = { | |||
| format: PropTypes.any, | |||
| icon: PropTypes.any, | |||
| }; | |||
| export default MarkButton; | |||
| @@ -0,0 +1,27 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled, { css } from "styled-components"; | |||
| import selectedTheme from "../../../themes"; | |||
| export const MarkButtonContainer = styled(Box)` | |||
| display: inline; | |||
| cursor: pointer; | |||
| `; | |||
| export const MarkButtonIcon = styled(Box)` | |||
| font-size: 16px; | |||
| font-family: "Source Code Pro"; | |||
| ${(props) => | |||
| props.format === "bold" | |||
| ? `font-weight: bold;` | |||
| : props.format === "italic" | |||
| ? "font-style: italic;" | |||
| : props.format === "underline" | |||
| ? `text-decoration: underline;` | |||
| : ""} | |||
| color: ${selectedTheme.colors.primaryGrayText}; | |||
| line-height: 20px; | |||
| ${(props) => | |||
| props.active && | |||
| css` | |||
| color: ${selectedTheme.colors.primaryText}; | |||
| `} | |||
| `; | |||
| @@ -0,0 +1,114 @@ | |||
| import React, { useCallback, useMemo } from "react"; | |||
| import { Editable, withReact, Slate } from "slate-react"; | |||
| import { createEditor } from "slate"; | |||
| import PropTypes from "prop-types"; | |||
| import RichTextElement from "./RichTextElement/RichTextElement"; | |||
| import MarkButton from "./MarkButton/MarkButton"; | |||
| import RichTextLeaf from "./RichTextLeaf/RichTextLeaf"; | |||
| import { ReactComponent as BulletedList } from "../../assets/images/svg/bulleted-list.svg"; | |||
| import { | |||
| EditableContainer, | |||
| EditableInnerContainer, | |||
| RichTextComponentContainer, | |||
| SlateButtonsContainer, | |||
| } from "./RichTextComponent.styled"; | |||
| import { useState } from "react"; | |||
| import BlockButton from "./BlockButton/BlockButton"; | |||
| import ColorButton from "./ColorButton/ColorButton"; | |||
| import selectedTheme from "../../themes"; | |||
| import useIsMobile from "../../hooks/useIsMobile"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { isJsonString } from "../../util/helpers/jsonHelper"; | |||
| import { useEffect } from "react"; | |||
| const RichTextComponent = (props) => { | |||
| const { isMobile } = useIsMobile(); | |||
| const { t } = useTranslation(); | |||
| const [selectedColor, setSelectedColor] = useState({ | |||
| color: selectedTheme.colors.colorPicker.darkGray, | |||
| colorName: "darkGray", | |||
| }); | |||
| const [value, setValue] = useState([ | |||
| { | |||
| type: "paragraph", | |||
| children: [{ text: "" }], | |||
| }, | |||
| ]); | |||
| useEffect(() => { | |||
| setValue(props?.value); | |||
| editor.children = isJsonString(props?.value) | |||
| ? JSON.parse(props?.value) | |||
| : value; | |||
| }, [props?.value]); | |||
| const renderElement = useCallback( | |||
| (props) => <RichTextElement {...props} />, | |||
| [] | |||
| ); | |||
| const renderLeaf = useCallback((props) => <RichTextLeaf {...props} />, []); | |||
| const editor = useMemo(() => withReact(createEditor()), [props?.value]); | |||
| return ( | |||
| <RichTextComponentContainer | |||
| offerCard={props?.offerCard} | |||
| readOnly={props?.readOnly} | |||
| itemDetails={props?.itemDetails} | |||
| > | |||
| <Slate | |||
| editor={editor} | |||
| value={isJsonString(props?.value) ? JSON.parse(props?.value) : value} | |||
| onChange={(newValue) => { | |||
| if (props?.onChange) props?.onChange(JSON.stringify(newValue)); | |||
| else setValue(newValue); | |||
| }} | |||
| > | |||
| {!isMobile && ( | |||
| <SlateButtonsContainer readOnly={props?.readOnly}> | |||
| <MarkButton format="bold" icon="B" /> | |||
| <MarkButton format="italic" icon="I" /> | |||
| <MarkButton format="underline" icon="U" /> | |||
| <BlockButton format="bulleted-list" icon={<BulletedList />} /> | |||
| <ColorButton | |||
| selectedColor={selectedColor} | |||
| setSelectedColor={setSelectedColor} | |||
| /> | |||
| </SlateButtonsContainer> | |||
| )} | |||
| <EditableContainer | |||
| offerCard={props?.offerCard} | |||
| itemDetails={props?.itemDetails} | |||
| > | |||
| <EditableInnerContainer readOnly={props?.readOnly}> | |||
| <Editable | |||
| placeholder={t("offer.description")} | |||
| renderElement={renderElement} | |||
| renderLeaf={renderLeaf} | |||
| spellCheck | |||
| autoFocus | |||
| readOnly={props?.readOnly} | |||
| /> | |||
| </EditableInnerContainer> | |||
| </EditableContainer> | |||
| </Slate> | |||
| </RichTextComponentContainer> | |||
| ); | |||
| }; | |||
| // const initialValue = [ | |||
| // { | |||
| // type: "paragraph", | |||
| // children: [{ text: "" }], | |||
| // }, | |||
| // ]; | |||
| RichTextComponent.propTypes = { | |||
| children: PropTypes.node, | |||
| onChange: PropTypes.func, | |||
| value: PropTypes.any, | |||
| readOnly: PropTypes.bool, | |||
| offerCard: PropTypes.bool, | |||
| itemDetails: PropTypes.bool, | |||
| }; | |||
| export default RichTextComponent; | |||
| @@ -0,0 +1,76 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled, { css } from "styled-components"; | |||
| import selectedTheme from "../../themes"; | |||
| export const SlateButtonsContainer = styled(Box)` | |||
| display: ${(props) => (props.readOnly ? "none" : "flex")}; | |||
| flex-direction: row; | |||
| height: 36px; | |||
| background-color: ${selectedTheme.colors.stylingTextBackground}; | |||
| padding: 8px 16px; | |||
| border-radius: 4px; | |||
| gap: 20px; | |||
| `; | |||
| export const RichTextComponentContainer = styled(Box)` | |||
| width: 100%; | |||
| position: relative; | |||
| top: 14px; | |||
| height: ${(props) => | |||
| props?.offerCard || props?.itemDetails ? "auto" : "140px"}; | |||
| ${(props) => | |||
| props?.offerCard && | |||
| css` | |||
| max-height: 100px; | |||
| display: -webkit-box; | |||
| -webkit-line-clamp: 4; | |||
| -webkit-box-orient: vertical; | |||
| `} | |||
| border-radius: 4px; | |||
| border: ${(props) => | |||
| props.readOnly ? "0" : `1px solid ${selectedTheme.colors.borderNormal}`}; | |||
| margin-bottom: 14px; | |||
| @media (max-width: 600px) { | |||
| /* height: 40px; */ | |||
| height: auto; | |||
| } | |||
| `; | |||
| export const EditableContainer = styled(Box)` | |||
| ${(props) => !props?.itemDetails && "max-height: 104px;"} | |||
| ${(props) => !props?.offerCard && "overflow: auto;"} | |||
| &::-webkit-scrollbar { | |||
| width: 5px; | |||
| height: 5px; | |||
| } | |||
| &::-webkit-scrollbar-track { | |||
| background: #ddd; | |||
| } | |||
| &::-webkit-scrollbar-thumb { | |||
| background: #777; | |||
| } | |||
| scrollbar-width: thin; | |||
| scrollbar-color: #ddd; | |||
| @media (max-width: 600px) { | |||
| /* max-height: 39px; */ | |||
| } | |||
| `; | |||
| export const EditableInnerContainer = styled(Box)` | |||
| padding: ${(props) => (props?.readOnly ? "0" : "10px 16px")}; | |||
| & > div { | |||
| min-height: 84px; | |||
| } | |||
| & * { | |||
| font-family: ${selectedTheme.fonts.textFont}; | |||
| } | |||
| & span[data-slate-placeholder="true"] { | |||
| font-style: italic; | |||
| color: ${selectedTheme.colors.colorPicker.darkGray}; | |||
| } | |||
| @media (max-width: 600px) { | |||
| & > div { | |||
| min-height: 0; | |||
| } | |||
| & * { | |||
| font-size: 12px; | |||
| } | |||
| } | |||
| `; | |||
| @@ -0,0 +1,70 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { ListContainer } from "./RichTextElement.styled"; | |||
| import selectedTheme from "../../../themes"; | |||
| const RichTextElement = ({ attributes, children, element }) => { | |||
| const style = { textAlign: element.align }; | |||
| switch (element.type) { | |||
| case "block-quote": | |||
| return ( | |||
| <blockquote style={style} {...attributes}> | |||
| {children} | |||
| </blockquote> | |||
| ); | |||
| case "bulleted-list": | |||
| return ( | |||
| <ListContainer style={style} {...attributes}> | |||
| {children} | |||
| </ListContainer> | |||
| ); | |||
| case "link": | |||
| return ( | |||
| <a href="" style={style} {...attributes}> | |||
| {children} | |||
| </a> | |||
| ); | |||
| case "heading-one": | |||
| return ( | |||
| <h1 style={style} {...attributes}> | |||
| {children} | |||
| </h1> | |||
| ); | |||
| case "heading-two": | |||
| return ( | |||
| <h2 style={style} {...attributes}> | |||
| {children} | |||
| </h2> | |||
| ); | |||
| case "list-item": | |||
| return ( | |||
| <li style={style} {...attributes}> | |||
| {children} | |||
| </li> | |||
| ); | |||
| case "numbered-list": | |||
| return ( | |||
| <ol style={style} {...attributes}> | |||
| {children} | |||
| </ol> | |||
| ); | |||
| default: | |||
| return ( | |||
| <div | |||
| style={style} | |||
| {...attributes} | |||
| color={selectedTheme.colors.colorPicker.darkGray} | |||
| > | |||
| {children} | |||
| </div> | |||
| ); | |||
| } | |||
| }; | |||
| RichTextElement.propTypes = { | |||
| attributes: PropTypes.any, | |||
| children: PropTypes.any, | |||
| element: PropTypes.any, | |||
| }; | |||
| export default RichTextElement; | |||
| @@ -0,0 +1,10 @@ | |||
| import { Box } from "@mui/material"; | |||
| import styled from "styled-components"; | |||
| export const ListContainer = styled(Box)` | |||
| color: green; | |||
| padding-left: 18px; | |||
| & > div { | |||
| display: list-item; | |||
| } | |||
| `; | |||
| @@ -0,0 +1,31 @@ | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| const RichTextLeaf = ({ attributes, children, leaf }) => { | |||
| if (leaf.bold) { | |||
| children = <strong>{children}</strong>; | |||
| } | |||
| if (leaf.code) { | |||
| children = <code>{children}</code>; | |||
| } | |||
| if (leaf.italic) { | |||
| children = <i>{children}</i>; | |||
| } | |||
| if (leaf.underline) { | |||
| children = <u>{children}</u>; | |||
| } | |||
| if (leaf.color) { | |||
| children = <span style={{ color: leaf.color }}>{children}</span>; | |||
| } | |||
| return <span {...attributes}>{children}</span>; | |||
| }; | |||
| RichTextLeaf.propTypes = { | |||
| attributes: PropTypes.any, | |||
| children: PropTypes.any, | |||
| leaf: PropTypes.any, | |||
| selectedColor: PropTypes.any, | |||
| }; | |||
| export default RichTextLeaf; | |||
| @@ -14,6 +14,7 @@ export const TextField = React.forwardRef((props, ref) => { | |||
| setIsFieldEmpty(false); | |||
| } | |||
| }, [props.value]); | |||
| console.log(props); | |||
| return ( | |||
| <TextFieldContainer | |||
| style={props.containerStyle} | |||
| @@ -21,6 +22,7 @@ export const TextField = React.forwardRef((props, ref) => { | |||
| height={props.height} | |||
| > | |||
| <TextFieldStyled | |||
| {...props.attributes} | |||
| inputRef={ref} | |||
| inputProps={props.inputProps} | |||
| placeholder={props.placeholder} | |||
| @@ -98,11 +100,12 @@ TextField.propTypes = { | |||
| endAdornment: PropTypes.node, | |||
| style: PropTypes.any, | |||
| }), | |||
| attributes: PropTypes.any, | |||
| }; | |||
| TextField.defaultProps = { | |||
| italicPlaceholder: false, | |||
| showAnimation: false, | |||
| height: "48px", | |||
| font: selectedTheme.fonts.textFont | |||
| font: selectedTheme.fonts.textFont, | |||
| }; | |||
| @@ -26,3 +26,4 @@ export const ADMIN_CATEGORIES_PAGE = "/admin/categories"; | |||
| export const ADMIN_LOCATIONS_PAGE = "/admin/locations"; | |||
| export const ADMIN_PAYMENT_PAGE = "/admin/payment"; | |||
| export const ADMIN_SUBCATEGORIES_PAGE = "/admin/categories/:categoryId"; | |||
| export const MARKETPLACE_PAGE = "/marketplace"; | |||
| @@ -0,0 +1,12 @@ | |||
| import { useSelector } from "react-redux"; | |||
| import { selectUserId } from "../store/selectors/loginSelectors"; | |||
| const useIsLoggedIn = () => { | |||
| const userId = useSelector(selectUserId); | |||
| const isLoggedIn = userId ? true : false; | |||
| return { | |||
| isLoggedIn | |||
| } | |||
| }; | |||
| export default useIsLoggedIn; | |||
| @@ -603,4 +603,12 @@ export default { | |||
| prevPage: "Previous page", | |||
| nextPage: "Next page", | |||
| }, | |||
| colorPicker: { | |||
| label: "Boja: ", | |||
| darkGray: "Tamno Siva", | |||
| gray: "Siva", | |||
| yellow: "Žuta", | |||
| purple: "Ljubičasta", | |||
| pink: "Roze" | |||
| } | |||
| }; | |||
| @@ -1,92 +1,9 @@ | |||
| import React, { useEffect, useRef } from "react"; | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import AboutComponent from "../../components/About/AboutComponent"; | |||
| import { AboutPageContainer } from "./AboutPage.styled"; | |||
| // import PricesComponent from "../../components/Prices/PricesComponent"; | |||
| import PrivacyPolicyComponent from "../../components/PrivacyPolicy/PrivacyPolicyComponent"; | |||
| import PricesComponent from "../../components/Prices/PricesComponent"; | |||
| import { useLocation } from "react-router-dom"; | |||
| import scrollConstants from "../../constants/scrollConstants"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectAboutRouteSelected } from "../../store/selectors/appSelectors"; | |||
| import { setAboutRouteSelected } from "../../store/actions/app/appActions"; | |||
| import AboutFooter from "../../components/Footer/AboutFooter"; | |||
| import AboutPageContent from "../../components/About/AboutPageContent"; | |||
| const AboutPage = () => { | |||
| const aboutRef = useRef(null); | |||
| const pricesRef = useRef(null); | |||
| const privacyPolicyRef = useRef(null); | |||
| const dispatch = useDispatch(); | |||
| const aboutRouteSelected = useSelector(selectAboutRouteSelected); | |||
| const location = useLocation(); | |||
| useEffect(() => { | |||
| if (location.state && location.state?.clicked) { | |||
| if (location.state.navigation === scrollConstants.about.aboutPage) { | |||
| window.scrollTo({ top: 0, behavior: "smooth" }); | |||
| if (aboutRouteSelected !== scrollConstants.about.aboutPage) { | |||
| dispatch(setAboutRouteSelected(scrollConstants.about.aboutPage)); | |||
| } | |||
| } | |||
| if (location.state.navigation === scrollConstants.about.pricesPage) { | |||
| const yAxis = pricesRef.current.offsetTop; | |||
| window.scrollTo({ top: yAxis, behavior: "smooth" }); | |||
| if (aboutRouteSelected !== scrollConstants.about.pricesPage) { | |||
| dispatch(setAboutRouteSelected(scrollConstants.about.pricesPage)); | |||
| } | |||
| } | |||
| if ( | |||
| location.state.navigation === scrollConstants.about.privacyPolicyPage | |||
| ) { | |||
| const yAxis = privacyPolicyRef.current.offsetTop - 64; | |||
| window.scrollTo({ top: yAxis, behavior: "smooth" }); | |||
| if (aboutRouteSelected !== scrollConstants.about.privacyPolicyPage) { | |||
| dispatch( | |||
| setAboutRouteSelected(scrollConstants.about.privacyPolicyPage) | |||
| ); | |||
| } | |||
| } | |||
| location.state = {}; | |||
| } | |||
| }, [location]); | |||
| useEffect(() => { | |||
| const listener = () => { | |||
| if ( | |||
| window.scrollY > | |||
| pricesRef.current.offsetTop - window.innerHeight / 2 | |||
| ) { | |||
| if ( | |||
| window.scrollY > | |||
| privacyPolicyRef.current.offsetTop - window.innerHeight / 2 | |||
| ) { | |||
| if (aboutRouteSelected !== scrollConstants.about.privacyPolicyPage) { | |||
| dispatch( | |||
| setAboutRouteSelected(scrollConstants.about.privacyPolicyPage) | |||
| ); | |||
| } | |||
| } else if (aboutRouteSelected !== scrollConstants.about.pricesPage) { | |||
| dispatch(setAboutRouteSelected(scrollConstants.about.pricesPage)); | |||
| } | |||
| } else { | |||
| if (aboutRouteSelected !== scrollConstants.about.aboutPage) { | |||
| dispatch(setAboutRouteSelected(scrollConstants.about.aboutPage)); | |||
| } | |||
| } | |||
| }; | |||
| window.addEventListener("scroll", listener); | |||
| return () => window.removeEventListener("scroll", listener); | |||
| }, [aboutRouteSelected]); | |||
| return ( | |||
| <AboutPageContainer> | |||
| <AboutComponent ref={aboutRef} id={scrollConstants.about.aboutPage} /> | |||
| <PricesComponent ref={pricesRef} id={scrollConstants.about.pricesPage} /> | |||
| <PrivacyPolicyComponent | |||
| ref={privacyPolicyRef} | |||
| id={scrollConstants.about.privacyPolicyPage} | |||
| /> | |||
| <AboutFooter /> | |||
| </AboutPageContainer> | |||
| ); | |||
| return <AboutPageContent /> | |||
| }; | |||
| AboutPage.propTypes = { | |||
| @@ -1,12 +0,0 @@ | |||
| import { Box } from "@mui/system"; | |||
| import styled from "styled-components"; | |||
| import selectedTheme from "../../themes"; | |||
| export const AboutPageContainer = styled(Box)` | |||
| margin-top: 64px; | |||
| background-color: ${selectedTheme.colors.staticBackgroundColor}; | |||
| position: relative; | |||
| @media (max-width: 600px) { | |||
| margin-top: 53px; | |||
| } | |||
| ` | |||
| @@ -1,4 +1,5 @@ | |||
| import React, { useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { HomePageContainer } from "./HomePage.styled"; | |||
| import FilterCard from "../../components/Cards/FilterCard/FilterCard"; | |||
| import MainLayout from "../../layouts/MainLayout/MainLayout"; | |||
| @@ -7,8 +8,11 @@ import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelect | |||
| import { useSelector } from "react-redux"; | |||
| import { OFFERS_SCOPE } from "../../store/actions/offers/offersActionConstants"; | |||
| import useOffers from "../../hooks/useOffers/useOffers"; | |||
| import useIsLoggedIn from "../../hooks/useIsLoggedIn"; | |||
| import AboutPageContent from "../../components/About/AboutPageContent"; | |||
| const HomePage = () => { | |||
| const HomePage = (props) => { | |||
| const { isLoggedIn } = useIsLoggedIn(); | |||
| const isLoadingOffers = useSelector( | |||
| selectIsLoadingByActionType(OFFERS_SCOPE) | |||
| ); | |||
| @@ -17,26 +21,33 @@ const HomePage = () => { | |||
| const toggleFilters = () => { | |||
| setFiltersOpened((prevFiltersOpened) => !prevFiltersOpened); | |||
| }; | |||
| return ( | |||
| <HomePageContainer> | |||
| <MainLayout | |||
| leftCard={ | |||
| <FilterCard | |||
| offers={offers} | |||
| filtersOpened={filtersOpened} | |||
| skeleton={isLoadingOffers} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| content={ | |||
| <MarketPlace | |||
| offers={offers} | |||
| skeleton={isLoadingOffers} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| /> | |||
| </HomePageContainer> | |||
| ); | |||
| if (isLoggedIn || props?.isMarketplacePage) { | |||
| return ( | |||
| <HomePageContainer> | |||
| <MainLayout | |||
| leftCard={ | |||
| <FilterCard | |||
| offers={offers} | |||
| filtersOpened={filtersOpened} | |||
| skeleton={isLoadingOffers} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| content={ | |||
| <MarketPlace | |||
| offers={offers} | |||
| skeleton={isLoadingOffers} | |||
| toggleFilters={toggleFilters} | |||
| /> | |||
| } | |||
| /> | |||
| </HomePageContainer> | |||
| ); | |||
| } | |||
| return <AboutPageContent />; | |||
| }; | |||
| HomePage.propTypes = { | |||
| isMarketplacePage: PropTypes.bool, | |||
| }; | |||
| export default HomePage; | |||
| @@ -0,0 +1,8 @@ | |||
| import React from "react"; | |||
| import HomePage from "../HomePage/HomePage"; | |||
| const MarketplacePage = () => { | |||
| return <HomePage isMarketplacePage />; | |||
| }; | |||
| export default MarketplacePage; | |||
| @@ -48,6 +48,7 @@ import { | |||
| pinOfferSuccess, | |||
| pinOfferError, | |||
| addProfileOffers, | |||
| clearSelectedOffer, | |||
| // fetchAllOffersSuccess, | |||
| // fetchAllOffersError, | |||
| // setFeaturedOfferPage, | |||
| @@ -215,6 +216,7 @@ function* fetchOneOffer(payload) { | |||
| queryObject, | |||
| }); | |||
| if (!data?.data) throw new Error(); | |||
| yield put(clearSelectedOffer()); | |||
| yield put(fetchOneOfferSuccess(data?.data)); | |||
| } catch (e) { | |||
| console.log(e?.response?.status); | |||
| @@ -274,7 +276,7 @@ function* fetchProfileOffers(payload) { | |||
| } else { | |||
| queryString = yield select(selectQueryString); | |||
| } | |||
| console.log(queryString.toString()); | |||
| let data; | |||
| if (payload.payload.isAdmin) { | |||
| @@ -29,6 +29,7 @@ export const primaryThemeColors = { | |||
| skeletonItemColor: "#F4F4F4", | |||
| chatHeaderColor: "#F4F4F4", | |||
| messageBackground: "#F4F4F4", | |||
| stylingTextBackground: "#F4F4F4", | |||
| messageText: "#1D1D1D", | |||
| messageDate: "#949494", | |||
| messageMyDate: "#C4C4C4", | |||
| @@ -41,4 +42,12 @@ export const primaryThemeColors = { | |||
| blockedColor: "#E4E4E4", | |||
| blockedTextColor: "#D13333", | |||
| errorColor: "#d32f2f", | |||
| colorPicker: { | |||
| darkGray: "#4D4D4D", | |||
| gray: "#B4B4B4", | |||
| yellow: "#FEB005", | |||
| purple: "#5A3984", | |||
| pink: "#E5D0FF", | |||
| border: "#818181", | |||
| }, | |||
| }; | |||
| @@ -0,0 +1,8 @@ | |||
| export const isJsonString = (stringToCheck) => { | |||
| try { | |||
| JSON.parse(stringToCheck); | |||
| } catch (e) { | |||
| return false; | |||
| } | |||
| return true; | |||
| }; | |||
| @@ -2178,6 +2178,11 @@ | |||
| "resolved" "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz" | |||
| "version" "5.1.1" | |||
| "@types/is-hotkey@^0.1.1": | |||
| "integrity" "sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ==" | |||
| "resolved" "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.7.tgz" | |||
| "version" "0.1.7" | |||
| "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": | |||
| "integrity" "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" | |||
| "resolved" "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz" | |||
| @@ -2215,7 +2220,7 @@ | |||
| "resolved" "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" | |||
| "version" "0.0.29" | |||
| "@types/lodash@^4.14.175": | |||
| "@types/lodash@^4.14.149", "@types/lodash@^4.14.175": | |||
| "integrity" "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==" | |||
| "resolved" "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz" | |||
| "version" "4.14.182" | |||
| @@ -4023,6 +4028,11 @@ | |||
| "safe-buffer" "5.1.2" | |||
| "vary" "~1.1.2" | |||
| "compute-scroll-into-view@^1.0.17": | |||
| "integrity" "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==" | |||
| "resolved" "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz" | |||
| "version" "1.0.17" | |||
| "concat-map@0.0.1": | |||
| "integrity" "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" | |||
| "resolved" "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" | |||
| @@ -4811,6 +4821,11 @@ | |||
| dependencies: | |||
| "path-type" "^4.0.0" | |||
| "direction@^1.0.3": | |||
| "integrity" "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==" | |||
| "resolved" "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz" | |||
| "version" "1.0.4" | |||
| "dns-equal@^1.0.0": | |||
| "integrity" "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" | |||
| "resolved" "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz" | |||
| @@ -6599,6 +6614,11 @@ | |||
| "resolved" "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz" | |||
| "version" "5.1.8" | |||
| "immer@^9.0.6": | |||
| "integrity" "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==" | |||
| "resolved" "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz" | |||
| "version" "9.0.16" | |||
| "immer@^9.0.7": | |||
| "integrity" "sha512-ubBeqQutOSLIFCUBN03jGeOS6a3DoYlSYwYJTa+gSKEZKU5redJIqkIdZ3JVv/4RZpfcXdAWH5zCNLWPRv2WDw==" | |||
| "resolved" "https://registry.npmjs.org/immer/-/immer-9.0.14.tgz" | |||
| @@ -6958,6 +6978,11 @@ | |||
| dependencies: | |||
| "is-extglob" "^2.1.1" | |||
| "is-hotkey@^0.1.6": | |||
| "integrity" "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==" | |||
| "resolved" "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz" | |||
| "version" "0.1.8" | |||
| "is-installed-globally@^0.4.0": | |||
| "integrity" "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==" | |||
| "resolved" "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz" | |||
| @@ -7044,6 +7069,11 @@ | |||
| dependencies: | |||
| "isobject" "^3.0.1" | |||
| "is-plain-object@^5.0.0": | |||
| "integrity" "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" | |||
| "resolved" "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz" | |||
| "version" "5.0.0" | |||
| "is-potential-custom-element-name@^1.0.1": | |||
| "integrity" "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" | |||
| "resolved" "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" | |||
| @@ -8143,7 +8173,7 @@ | |||
| "resolved" "https://registry.npmjs.org/lodash.unset/-/lodash.unset-4.5.2.tgz" | |||
| "version" "4.5.2" | |||
| "lodash@^4.17.11", "lodash@^4.17.14", "lodash@^4.17.15", "lodash@^4.17.19", "lodash@^4.17.20", "lodash@^4.17.21", "lodash@^4.17.5", "lodash@^4.7.0", "lodash@>=3.5 <5", "lodash@4": | |||
| "lodash@^4.17.11", "lodash@^4.17.14", "lodash@^4.17.15", "lodash@^4.17.19", "lodash@^4.17.20", "lodash@^4.17.21", "lodash@^4.17.4", "lodash@^4.17.5", "lodash@^4.7.0", "lodash@>=3.5 <5", "lodash@4": | |||
| "integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" | |||
| "resolved" "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" | |||
| "version" "4.17.21" | |||
| @@ -10369,7 +10399,7 @@ | |||
| "strip-ansi" "6.0.0" | |||
| "text-table" "0.2.0" | |||
| "react-dom@*", "react-dom@^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.6.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0", "react-dom@^17.0.0 || ^18.0.0", "react-dom@^17.0.2", "react-dom@>= 16.8.0", "react-dom@>=16", "react-dom@>=16.6.0": | |||
| "react-dom@*", "react-dom@^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.6.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0", "react-dom@^17.0.0 || ^18.0.0", "react-dom@^17.0.2", "react-dom@>= 16.8.0", "react-dom@>=16", "react-dom@>=16.6.0", "react-dom@>=16.8.0": | |||
| "integrity" "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==" | |||
| "resolved" "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz" | |||
| "version" "17.0.2" | |||
| @@ -11337,6 +11367,13 @@ | |||
| "ajv" "^6.12.5" | |||
| "ajv-keywords" "^3.5.2" | |||
| "scroll-into-view-if-needed@^2.2.20": | |||
| "integrity" "sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg==" | |||
| "resolved" "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz" | |||
| "version" "2.2.29" | |||
| dependencies: | |||
| "compute-scroll-into-view" "^1.0.17" | |||
| "section-iterator@^2.0.0": | |||
| "integrity" "sha512-xvTNwcbeDayXotnV32zLb3duQsP+4XosHpb/F+tu6VzEZFmIjzPdNk6/O+QOOx5XTh08KL2ufdXeCO33p380pQ==" | |||
| "resolved" "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz" | |||
| @@ -11609,6 +11646,29 @@ | |||
| "resolved" "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" | |||
| "version" "3.0.0" | |||
| "slate-react@^0.86.0": | |||
| "integrity" "sha512-FlzG0CL1MtpZk7DRH6a101FdqdhTi+vZ23SbY5hQudIdgLo7QnUBv97BRe30VctqsL9L+Z2OsFHSCmWpIIxJAw==" | |||
| "resolved" "https://registry.npmjs.org/slate-react/-/slate-react-0.86.0.tgz" | |||
| "version" "0.86.0" | |||
| dependencies: | |||
| "@types/is-hotkey" "^0.1.1" | |||
| "@types/lodash" "^4.14.149" | |||
| "direction" "^1.0.3" | |||
| "is-hotkey" "^0.1.6" | |||
| "is-plain-object" "^5.0.0" | |||
| "lodash" "^4.17.4" | |||
| "scroll-into-view-if-needed" "^2.2.20" | |||
| "tiny-invariant" "1.0.6" | |||
| "slate@^0.86.0", "slate@>=0.65.3": | |||
| "integrity" "sha512-aexL720Tpqx6St5oz0jo0/wZWCT7z8lChKkVJo2eiFkSNmoSnCGR62d2itAvmse7dEXkl4DbfAxRQPj8JDuGTg==" | |||
| "resolved" "https://registry.npmjs.org/slate/-/slate-0.86.0.tgz" | |||
| "version" "0.86.0" | |||
| dependencies: | |||
| "immer" "^9.0.6" | |||
| "is-plain-object" "^5.0.0" | |||
| "tiny-warning" "^1.0.3" | |||
| "slice-ansi@^4.0.0": | |||
| "integrity" "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==" | |||
| "resolved" "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" | |||
| @@ -12398,6 +12458,11 @@ | |||
| "resolved" "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz" | |||
| "version" "1.2.0" | |||
| "tiny-invariant@1.0.6": | |||
| "integrity" "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" | |||
| "resolved" "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz" | |||
| "version" "1.0.6" | |||
| "tiny-warning@^1.0.0", "tiny-warning@^1.0.2", "tiny-warning@^1.0.3": | |||
| "integrity" "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" | |||
| "resolved" "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz" | |||