| @@ -1,70 +1,81 @@ | |||
| # Getting Started with Create React App | |||
| # Trampa API Frontend | |||
| This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). | |||
| ## Available Scripts | |||
| Welcome to frontend application for trampa website. In next sections we will walk you through our project, setting it up on your computer, building it for production and last but not the least walk you through tools that we used: | |||
| In the project directory, you can run: | |||
| - [Description](#Description) | |||
| - [Setup](#Setup) | |||
| - [Testing](#Testing) | |||
| ### `npm start` | |||
| # Description | |||
| This is an internal project for e-commerce website called Trampa that was done by Diligent. | |||
| Our project is hosted on our local [git](https://git.dilig.net/selenaaasi/trampa-frontend) | |||
| Runs the app in the development mode.\ | |||
| Open [http://localhost:3000](http://localhost:3000) to view it in the browser. | |||
| ## Tech stack | |||
| The page will reload if you make edits.\ | |||
| You will also see any lint errors in the console. | |||
| In this project we used Node and N(ode)P(ackage)M(anager) for initializing project | |||
| and importing a lot of libraries that helps us developing. | |||
| ### `npm test` | |||
| This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). | |||
| Every HTTP request is implemented via Axios, and for handling asynchronous code we used Saga. | |||
| Launches the test runner in the interactive watch mode.\ | |||
| See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. | |||
| ## Node Package Manager | |||
| ### `npm run build` | |||
| In order to run this project you need to install Node that comes with its Package Manager. | |||
| Please follow the official tutorial on installing Node which can be found on this link: | |||
| > https://docs.npmjs.com/downloading-and-installing-node-js-and-npm | |||
| Builds the app for production to the `build` folder.\ | |||
| It correctly bundles React in production mode and optimizes the build for the best performance. | |||
| ## Development environment | |||
| - [Visual Studio Code](https://code.visualstudio.com/Download) - Used as the primary code editor | |||
| - [npm](https://www.npmjs.com/) - Used for installing node and running our application | |||
| - [git](https://git-scm.com/downloads) | |||
| The build is minified and the filenames include the hashes.\ | |||
| Your app is ready to be deployed! | |||
| # Setup | |||
| See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. | |||
| Here we will show you how to set up project and run it in localhost. | |||
| ## Project setup | |||
| In order to run our project you need to clone it from [git](https://git.dilig.net/selenaaasi/trampa-frontend.git) first. Open terminal (cmd and powershell work as well) in folder that you want this project to be and run command | |||
| > git clone http://git.dilig.net/selenaaasi/trampa-frontend.git | |||
| ### `npm run eject` | |||
| After cloning project you can open it with your preferred IDE/Code editor if you want to see the code. Before running project you need to open terminal and run command | |||
| > npm install | |||
| **Note: this is a one-way operation. Once you `eject`, you can’t go back!** | |||
| Running that command will download all necessary npm packages to run the project. | |||
| If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. | |||
| Now your project is ready for setup on both local and production environment. | |||
| Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. | |||
| ### Local project setup | |||
| You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. | |||
| You can run the project using | |||
| > npm start | |||
| ## Learn More | |||
| Congratulations! You now run website application on following website: | |||
| > http://localhost:3000/ | |||
| You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). | |||
| ### Production project setup | |||
| To learn React, check out the [React documentation](https://reactjs.org/). | |||
| You can make the production build using | |||
| > npm run build | |||
| ### Code Splitting | |||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) | |||
| ### Analyzing the Bundle Size | |||
| Congratulations! You now have production build which is ready for deploy! | |||
| The build is minified and the filenames include the hashes. | |||
| It correctly bundles React in production mode and optimizes the build for the best performance. | |||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) | |||
| When your application is deployed you can run it using: | |||
| > npx serve -s build | |||
| ### Making a Progressive Web App | |||
| See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. | |||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) | |||
| # Testing | |||
| Here we will show you how to run tests and see code coverage | |||
| ### Advanced Configuration | |||
| ## Testing framework | |||
| We needed to write tests, unit, integration and E2E. Framework that we used for that is called [jest](https://www.npmjs.com/package/jest). | |||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) | |||
| When we write our tests we can use following comand: | |||
| > npm test | |||
| ### Deployment | |||
| which launches the test runner in the interactive watch mode.\ | |||
| See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. | |||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) | |||
| ### `npm run build` fails to minify | |||
| This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) | |||
| @@ -1,6 +1,6 @@ | |||
| { | |||
| "name": "web", | |||
| "version": "3.0.4", | |||
| "version": "3.0.6", | |||
| "private": true, | |||
| "dependencies": { | |||
| "@emotion/react": "^11.5.0", | |||
| @@ -1,6 +1,5 @@ | |||
| /*eslint-disable*/ | |||
| import React, { useState, useEffect } from "react"; | |||
| import io from "socket.io-client"; | |||
| import { Router } from "react-router-dom"; | |||
| import { Helmet } from "react-helmet-async"; | |||
| import i18next from "i18next"; | |||
| @@ -11,120 +10,17 @@ import { StyledEngineProvider } from "@mui/material"; | |||
| import GlobalStyle from "./components/Styles/globalStyles"; | |||
| import { ToastContainer } from "react-toastify"; | |||
| import "react-toastify/dist/ReactToastify.css"; | |||
| import { socketInit } from "./socket/socket"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectUserId } from "./store/selectors/loginSelectors"; | |||
| const URL = "https://trampa-api-test.dilig.net"; | |||
| const socket2 = io("https://trampa-api-test.dilig.net", { autoConnect: true, reconnectionAttempts: 5 }); | |||
| const socket = io("https://trampa-api-test.dilig.net", { | |||
| autoConnect: true, | |||
| transports: ["websocket"], | |||
| reconnectionAttempts: 5, | |||
| }); | |||
| const socket4 = io("https://trampa-api-test.dilig.net", { autoConnect: true, reconnectionAttempts: 5 }); | |||
| const socket3 = io("https://trampa-api-test.dilig.net", { | |||
| autoConnect: true, | |||
| transports: ["websocket"], | |||
| reconnectionAttempts: 5, | |||
| }); | |||
| const App = () => { | |||
| console.log(socket); | |||
| console.log(socket2); | |||
| const userId = useSelector(selectUserId); | |||
| const [isConnected, setIsConnected] = useState(socket.connected); | |||
| const [lastPong, setLastPong] = useState(null); | |||
| console.log(); | |||
| useEffect(() => { | |||
| socket.auth = { | |||
| // userId: "62de57c6dff6f986e43d14ec", | |||
| userId: "62ff762554ec55060e3a456b", | |||
| sessionID: localStorage.getItem("sessionID"), | |||
| }; | |||
| socket.on("connect", (client) => { | |||
| console.log("client: ", client); | |||
| setIsConnected(true); | |||
| }); | |||
| socket2.on("connect", (client) => { | |||
| console.log("client: ", client); | |||
| setIsConnected(true); | |||
| }); | |||
| socket3.on("connect", (client) => { | |||
| console.log("client: ", client); | |||
| setIsConnected(true); | |||
| }); | |||
| socket4.on("connect", (client) => { | |||
| console.log("client: ", client); | |||
| setIsConnected(true); | |||
| }); | |||
| socket.on("session", ({ sessionID, userID }) => { | |||
| localStorage.setItem("sessionID", sessionID); | |||
| localStorage.setItem("userID", userID); | |||
| console.log("sessionID: ", sessionID); | |||
| console.log("userID: ", userID); | |||
| }); | |||
| // socket.on("connect_error", (err) => { | |||
| // console.log(err); | |||
| // }); | |||
| socketInit(userId); | |||
| }, [userId]); | |||
| socket.on("connection", (client) => { | |||
| console.log(client); | |||
| }); | |||
| socket.on("disconnect", () => { | |||
| setIsConnected(false); | |||
| }); | |||
| // socket.on("user disconnected", (userID) => { | |||
| // console.log(userID); | |||
| // }); | |||
| // // socket.on('emit', (client) => { | |||
| // // console.log(client); | |||
| // // }) | |||
| // socket.on("sokkk", (clg) => { | |||
| // console.log(clg); | |||
| // }); | |||
| // // socket.onAny((event, ...args) => { | |||
| // // console.log(event, args); | |||
| // // }); | |||
| // socket.on("povratna", (data) => { | |||
| // console.log(data); | |||
| // }); | |||
| // socket.on("private_message", (data) => { | |||
| // console.log(data); | |||
| // }); | |||
| // // socket.open; | |||
| // socket.on("pong", () => { | |||
| // setLastPong(new Date().toISOString()); | |||
| // }); | |||
| // // socket.connect(); | |||
| return () => { | |||
| socket.off("connect"); | |||
| socket.off("disconnect"); | |||
| socket.off("pong"); | |||
| socket.off("reconnection_attempt") | |||
| }; | |||
| }, []); | |||
| const handleClick = () => { | |||
| // socket.connect(); | |||
| // socket.emit("sokkk 2", "sock"); | |||
| // socket.emit("sock") | |||
| }; | |||
| const sendPing = () => { | |||
| socket.emit("private_message", { | |||
| text: "Probica", | |||
| // toUserId: "62de5844dff6f986e43d14f6", | |||
| toUserId: "62de57c6dff6f986e43d14ec", | |||
| chatId: "62eb8424632e1112ef467750", | |||
| }); | |||
| }; | |||
| const disconnect = () => { | |||
| // socket.disconnect(); | |||
| socket.disconnect(); | |||
| }; | |||
| return ( | |||
| <Router history={history}> | |||
| <Helmet> | |||
| @@ -134,15 +30,6 @@ const App = () => { | |||
| <Header /> | |||
| <GlobalStyle /> | |||
| <ToastContainer /> | |||
| {/* <div style={{ position: "relative", top: "100px", left: "400px" }}> | |||
| <p>Connected: {"" + isConnected}</p> | |||
| <br /> | |||
| <p>Last pong: {lastPong || "-"}</p> | |||
| <br /> | |||
| <button onClick={sendPing}>Send ping</button> | |||
| <br /> | |||
| <button onClick={disconnect}>Disconnect</button> | |||
| </div> */} | |||
| <AppRoutes /> | |||
| </StyledEngineProvider> | |||
| </Router> | |||
| @@ -16,6 +16,7 @@ import MobileOfferDetails from "./MobileOfferDetails/MobileOfferDetails"; | |||
| import OfferLocation from "./OfferLocation/OfferLocation"; | |||
| import ChatCommands from "./ChatCommands/ChatCommands"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| const ChatCard = (props) => { | |||
| const history = useHistory(); | |||
| @@ -40,7 +41,8 @@ const ChatCard = (props) => { | |||
| > | |||
| <Col> | |||
| <UserImgWrapper> | |||
| <UserImage src={chat?.interlocutorData?.image} /> | |||
| {/* <UserImage src={chat?.interlocutorData?.image} /> */} | |||
| <UserImage src={getImageUrl(chat?.interlocutorData?.image, variants.chatCard, isMobile)} /> | |||
| </UserImgWrapper> | |||
| <ChatInfo> | |||
| @@ -20,7 +20,6 @@ const ChatCommands = (props) => { | |||
| <Commands> | |||
| <PhoneIconContainer | |||
| onClick={(event) => { | |||
| console.log(event); | |||
| setShowPhonePopover(true); | |||
| setPhonePopoverAnchorEl(event.currentTarget); | |||
| }} | |||
| @@ -10,15 +10,25 @@ import { | |||
| } from "./LittleOfferDetails.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { Col } from "../ChatCard.styled"; | |||
| import { getImageUrl, variants } from "../../../../util/helpers/imageUrlGetter"; | |||
| import useIsMobile from "../../../../hooks/useIsMobile"; | |||
| const LittleOfferDetails = (props) => { | |||
| const chat = props.chat; | |||
| const { t } = useTranslation(); | |||
| const { isMobile } = useIsMobile(); | |||
| return ( | |||
| <Col mobileDisappear> | |||
| <ChatOffer> | |||
| <OfferImgWrapper> | |||
| <OfferImage src={chat?.offerData?.firstImage} /> | |||
| {/* <OfferImage src={chat?.offerData?.firstImage} /> */} | |||
| <OfferImage | |||
| src={getImageUrl( | |||
| chat?.offerData?.firstImage, | |||
| variants.offerCard, | |||
| isMobile | |||
| )} | |||
| /> | |||
| </OfferImgWrapper> | |||
| <OfferCardContainer> | |||
| <OfferText>{t("messages.cardProduct")}</OfferText> | |||
| @@ -53,9 +53,9 @@ const CreateOffer = ({ closeCreateOfferModal, editOffer, offer }) => { | |||
| .filter((img) => img !== undefined) | |||
| .map((img) => | |||
| img | |||
| .replace("data:image/jpg;base64,", "") | |||
| .replace("data:image/jpeg;base64,", "") | |||
| .replace("data:image/png;base64,", "") | |||
| // .replace("data:image/jpg;base64,", "") | |||
| // .replace("data:image/jpeg;base64,", "") | |||
| // .replace("data:image/png;base64,", "") | |||
| ); | |||
| const offerData = { | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useEffect } from "react"; | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { FilterFooterContainer } from "./FilterFooter.styled"; | |||
| import selectedTheme from "../../../../themes"; | |||
| @@ -8,15 +8,13 @@ import useIsMobile from "../../../../hooks/useIsMobile"; | |||
| const FilterFooter = (props) => { | |||
| const { t } = useTranslation(); | |||
| const {isMobile }= useIsMobile(); | |||
| const { isMobile } = useIsMobile(); | |||
| const filters = props.filters; | |||
| const handleFilters = () => { | |||
| filters.paging.changePage(1); | |||
| filters.apply(); | |||
| props.toggleFilters(); | |||
| }; | |||
| useEffect(() => { | |||
| console.log(isMobile); | |||
| }, [isMobile]) | |||
| return ( | |||
| <FilterFooterContainer responsiveOpen={isMobile}> | |||
| {isMobile && ( | |||
| @@ -15,11 +15,14 @@ import { | |||
| import { useTranslation } from "react-i18next"; | |||
| import useScreenDimensions from "../../../../hooks/useScreenDimensions"; | |||
| import { formatDateLocale } from "../../../../util/helpers/dateHelpers"; | |||
| import useIsMobile from "../../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../../util/helpers/imageUrlGetter"; | |||
| const OfferDetails = (props) => { | |||
| const offer = props.offer; | |||
| const { t } = useTranslation(); | |||
| const dimension = useScreenDimensions(); | |||
| const { isMobile } = useIsMobile(); | |||
| const date = formatDateLocale(new Date(offer?.offer?._created)); | |||
| return ( | |||
| <Details | |||
| @@ -29,15 +32,21 @@ const OfferDetails = (props) => { | |||
| > | |||
| {dimension.width < 600 || !props.singleOffer ? ( | |||
| <ScrollerHorizontal> | |||
| {offer?.offer?.images?.map((item) => { | |||
| return <OfferImage src={item} key={item} />; | |||
| })} | |||
| {offer?.offer?.images?.map((item) => ( | |||
| <OfferImage | |||
| src={getImageUrl(item, variants.offerCard, isMobile)} | |||
| key={item} | |||
| /> | |||
| ))} | |||
| </ScrollerHorizontal> | |||
| ) : ( | |||
| <ScrollerVertical> | |||
| {offer?.offer?.images?.map((item) => { | |||
| return <OfferImage src={item} key={item} />; | |||
| })} | |||
| {offer?.offer?.images?.map((item) => ( | |||
| <OfferImage | |||
| src={getImageUrl(item, variants.offerCard, isMobile)} | |||
| key={item} | |||
| /> | |||
| ))} | |||
| </ScrollerVertical> | |||
| )} | |||
| <OfferInfoContainer singleOffer={props.singleOffer}> | |||
| @@ -1,28 +1,44 @@ | |||
| import React from 'react' | |||
| import PropTypes from 'prop-types' | |||
| import { LittleOfferCardContainer, OfferCategory, OfferCategoryIcon, OfferDetails, OfferImage, OfferName, OfferSwapsIcon, OfferSwapsIconContainer } from './LittleOfferCard.styled' | |||
| import React from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { | |||
| LittleOfferCardContainer, | |||
| OfferCategory, | |||
| OfferCategoryIcon, | |||
| OfferDetails, | |||
| OfferImage, | |||
| OfferName, | |||
| OfferSwapsIcon, | |||
| OfferSwapsIconContainer, | |||
| } from "./LittleOfferCard.styled"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| const LittleOfferCard = (props) => { | |||
| return ( | |||
| <LittleOfferCardContainer> | |||
| <OfferImage src={props.image} /> | |||
| <OfferDetails> | |||
| <OfferName>{props.name}</OfferName> | |||
| <OfferCategory> | |||
| <OfferCategoryIcon /> | |||
| {props.categoryName}</OfferCategory> | |||
| </OfferDetails> | |||
| <OfferSwapsIconContainer> | |||
| <OfferSwapsIcon /> | |||
| </OfferSwapsIconContainer> | |||
| </LittleOfferCardContainer> | |||
| ) | |||
| } | |||
| const { isMobile } = useIsMobile(); | |||
| return ( | |||
| <LittleOfferCardContainer> | |||
| {/* <OfferImage src={props.image} /> */} | |||
| <OfferImage | |||
| src={getImageUrl(props.image, variants.reviewCard, isMobile)} | |||
| /> | |||
| <OfferDetails> | |||
| <OfferName>{props.name}</OfferName> | |||
| <OfferCategory> | |||
| <OfferCategoryIcon /> | |||
| {props.categoryName} | |||
| </OfferCategory> | |||
| </OfferDetails> | |||
| <OfferSwapsIconContainer> | |||
| <OfferSwapsIcon /> | |||
| </OfferSwapsIconContainer> | |||
| </LittleOfferCardContainer> | |||
| ); | |||
| }; | |||
| LittleOfferCard.propTypes = { | |||
| image: PropTypes.string, | |||
| name: PropTypes.string, | |||
| categoryName: PropTypes.string, | |||
| } | |||
| image: PropTypes.string, | |||
| name: PropTypes.string, | |||
| categoryName: PropTypes.string, | |||
| }; | |||
| export default LittleOfferCard | |||
| export default LittleOfferCard; | |||
| @@ -8,19 +8,25 @@ import { | |||
| ProfileImage, | |||
| } from "./MessageCard.styled"; | |||
| import { formatDateTime } from "../../../util/helpers/dateHelpers"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| const MessageCard = (props) => { | |||
| const message = props.message; | |||
| const { isMobile } = useIsMobile(); | |||
| const dateString = formatDateTime(new Date(message._created)); | |||
| return ( | |||
| <MessageCardContainer isMyMessage={props.isMyMessage}> | |||
| <ProfileImage src={props.image} /> | |||
| <MessageContent isMyMessage={props.isMyMessage}> | |||
| <MessageText isMyMessage={props.isMyMessage}> | |||
| <MessageCardContainer ismymessage={props.isMyMessage}> | |||
| {/* <ProfileImage src={props.image} /> */} | |||
| <ProfileImage | |||
| src={getImageUrl(props.image, variants.chatMessage, isMobile)} | |||
| /> | |||
| <MessageContent ismymessage={props.isMyMessage}> | |||
| <MessageText ismymessage={props.isMyMessage}> | |||
| {props.message.text} | |||
| </MessageText> | |||
| <MessageDate isMyMessage={props.isMyMessage}>{dateString}</MessageDate> | |||
| <MessageDate ismymessage={props.isMyMessage}>{dateString}</MessageDate> | |||
| </MessageContent> | |||
| </MessageCardContainer> | |||
| ); | |||
| @@ -4,7 +4,7 @@ import selectedTheme from "../../../themes"; | |||
| export const MessageCardContainer = styled(Box)` | |||
| display: flex; | |||
| flex-direction: ${props => props.isMyMessage ? `row-reverse` : `row`}; | |||
| flex-direction: ${props => props.ismymessage ? `row-reverse` : `row`}; | |||
| margin-bottom: 18px; | |||
| `; | |||
| export const ProfileImage = styled.img` | |||
| @@ -18,11 +18,11 @@ export const ProfileImage = styled.img` | |||
| `; | |||
| export const MessageContent = styled(Box)` | |||
| background-color: ${(props) => | |||
| props.isMyMessage | |||
| props.ismymessage | |||
| ? selectedTheme.primaryPurple | |||
| : selectedTheme.messageBackground}; | |||
| border-radius: ${(props) => | |||
| props.isMyMessage ? "9px 0px 9px 9px" : "0px 9px 9px 9px"}; | |||
| props.ismymessage ? "9px 0px 9px 9px" : "0px 9px 9px 9px"}; | |||
| padding: 9px; | |||
| position: relative; | |||
| min-height: 65px; | |||
| @@ -36,10 +36,10 @@ export const MessageText = styled(Typography)` | |||
| font-family: "DM Sans"; | |||
| font-size: 16px; | |||
| line-height: 22px; | |||
| color: ${props => props.isMyMessage ? `white` : selectedTheme.messageText}; | |||
| color: ${props => props.ismymessage ? `white` : selectedTheme.messageText}; | |||
| `; | |||
| export const MessageDate = styled(Typography)` | |||
| color: ${props => props.isMyMessage ? selectedTheme.messageMyDate : selectedTheme.messageDate}; | |||
| color: ${props => props.ismymessage ? selectedTheme.messageMyDate : selectedTheme.messageDate}; | |||
| font-size: 12px; | |||
| font-style: italic; | |||
| position: absolute; | |||
| @@ -10,16 +10,26 @@ import { | |||
| } from "./MiniChatCard.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| const MiniChatCard = (props) => { | |||
| const { t } = useTranslation(); | |||
| const history = useHistory(); | |||
| const { isMobile } = useIsMobile(); | |||
| const changeChat = () => { | |||
| history.push(`/messages/${props?.chat?.chat?._id}`); | |||
| }; | |||
| return ( | |||
| <MiniChatCardContainer selected={props.selected} onClick={changeChat}> | |||
| <ProfileImage src={props?.chat?.interlocutorData?.image} /> | |||
| {/* <ProfileImage src={props?.chat?.interlocutorData?.image} /> */} | |||
| <ProfileImage | |||
| src={getImageUrl( | |||
| props?.chat?.interlocutorData?.image, | |||
| variants.chatCard, | |||
| isMobile | |||
| )} | |||
| /> | |||
| <ProfileDetails> | |||
| <ProfileName selected={props.selected}> | |||
| {props?.chat?.interlocutorData?.name} | |||
| @@ -26,12 +26,15 @@ import { | |||
| } from "../../../../store/actions/offers/offersActions"; | |||
| import { useTranslation, Trans } from "react-i18next"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import useIsMobile from "../../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../../util/helpers/imageUrlGetter"; | |||
| const DeleteOffer = (props) => { | |||
| const dispatch = useDispatch(); | |||
| const { t } = useTranslation(); | |||
| const history = useHistory(); | |||
| const userId = props.offer.userId; | |||
| const { isMobile } = useIsMobile(); | |||
| const offerId = props.offer._id; | |||
| const closeDeleteModalHandler = () => { | |||
| props.closeModalHandler(); | |||
| @@ -59,7 +62,14 @@ const DeleteOffer = (props) => { | |||
| <DeleteOfferContainer> | |||
| <OfferInfo> | |||
| <OfferImageContainer> | |||
| <OfferImage src={props.offer.images[0]} /> | |||
| {/* <OfferImage src={props.offer.images[0]} /> */} | |||
| <OfferImage | |||
| src={getImageUrl( | |||
| props.offer.images[0], | |||
| variants.deleteChat, | |||
| isMobile | |||
| )} | |||
| /> | |||
| </OfferImageContainer> | |||
| <OfferDescription> | |||
| <OfferDescriptionTitle>{props.offer.name}</OfferDescriptionTitle> | |||
| @@ -39,12 +39,15 @@ import { useHistory } from "react-router-dom"; | |||
| import CreateOffer from "../CreateOfferCard/CreateOffer"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| const OfferCard = (props) => { | |||
| const [deleteOfferModal, setDeleteOfferModal] = useState(false); | |||
| const [editOfferModal, setEditOfferModal] = useState(false); | |||
| const history = useHistory(); | |||
| const userId = useSelector(selectUserId); | |||
| const { isMobile } = useIsMobile(); | |||
| const routeToItem = (itemId) => { | |||
| history.push(`/proizvodi/${itemId}`); | |||
| @@ -99,7 +102,15 @@ const OfferCard = (props) => { | |||
| <OfferFlexContainer vertical={props.vertical}> | |||
| <OfferImageContainer vertical={props.vertical}> | |||
| <OfferImage | |||
| src={props?.offer?.images ? props?.offer?.images[0] : ""} | |||
| src={ | |||
| props?.offer?.images | |||
| ? getImageUrl( | |||
| props?.offer?.images[0], | |||
| variants.offerCard, | |||
| isMobile | |||
| ) | |||
| : "" | |||
| } | |||
| vertical={props.vertical} | |||
| ></OfferImage> | |||
| </OfferImageContainer> | |||
| @@ -18,10 +18,15 @@ import { | |||
| import { ListItem } from "@mui/material"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| import ReviewOffer from "./ReviewOffer/ReviewOffer"; | |||
| import { reviewEnum } from "../../../enums/reviewEnum"; | |||
| const UserReviewsCard = (props) => { | |||
| const { t } = useTranslation(); | |||
| const { isMobile } = useIsMobile(); | |||
| console.log(props); | |||
| const review = useMemo(() => { | |||
| if (props.givingReview) { | |||
| @@ -29,12 +34,24 @@ const UserReviewsCard = (props) => { | |||
| ...props.review, | |||
| }; | |||
| } | |||
| console.log(props.review); | |||
| let isSuccessfulSwap = "DA"; | |||
| if (props.review.succeeded === "failed") isSuccessfulSwap = "NE"; | |||
| if ( | |||
| props.review.succeeded === "failed" || | |||
| props.review.isSuccessfulSwap === reviewEnum.NO.mainText | |||
| ) | |||
| isSuccessfulSwap = "NE"; | |||
| let isGoodCommunication = "DA"; | |||
| if (props.review.communication === "could be better") | |||
| if ( | |||
| props.review.communication === "could be better" || | |||
| props.review.isCorrectCommunication === reviewEnum.NOT_BAD.mainText | |||
| ) | |||
| isGoodCommunication = "MOŽE BOLJE"; | |||
| if (props.review.communication === "no") isGoodCommunication = "NE"; | |||
| if ( | |||
| props.review.communication === "no" || | |||
| props.review.isCorrectCommunication === reviewEnum.NO.mainText | |||
| ) | |||
| isGoodCommunication = "NE"; | |||
| return { | |||
| name: props.review.companyName, | |||
| image: props.review.image, | |||
| @@ -48,7 +65,10 @@ const UserReviewsCard = (props) => { | |||
| <ReviewContainer key={review?.image}> | |||
| <ListItem alignItems="flex-start" sx={{ alignItems: "center", mt: 2 }}> | |||
| <ProfileImageContainer> | |||
| <ProfileImage alt={review?.name} src={review?.image} /> | |||
| {/* <ProfileImage alt={review?.name} src={review?.image} /> */} | |||
| <ProfileImage | |||
| src={getImageUrl(review?.image, variants.reviewCard, isMobile)} | |||
| /> | |||
| </ProfileImageContainer> | |||
| <ProfileName sx={{ color: selectedTheme.primaryPurple }}> | |||
| <b>{review?.name}</b> | |||
| @@ -63,7 +83,7 @@ const UserReviewsCard = (props) => { | |||
| sx={{ pl: 2, py: 2 }} | |||
| > | |||
| <ThumbBox item> | |||
| {review.isSuccessfulSwap === "DA" ? ( | |||
| {review.isSuccessfulSwap === reviewEnum.YES.mainText ? ( | |||
| <ThumbUp color="success" /> | |||
| ) : ( | |||
| <ThumbDown color="error" /> | |||
| @@ -18,8 +18,9 @@ import { HeaderTitle } from "../ProfileCard/ProfileCard.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectLatestChats } from "../../store/selectors/chatSelectors"; | |||
| import { fetchChats } from "../../store/actions/chat/chatActions"; | |||
| import { addNewMessage, fetchChats } from "../../store/actions/chat/chatActions"; | |||
| import useSorting from "../../hooks/useOffers/useSorting"; | |||
| import { addMesageListener, removeMessageListener } from "../../socket/socket"; | |||
| export const DownArrow = (props) => { | |||
| <IconStyled {...props}> | |||
| @@ -38,6 +39,16 @@ export const ChatColumn = () => { | |||
| dispatch(fetchChats()); | |||
| }, []); | |||
| useEffect(() => { | |||
| addMesageListener((data) => { | |||
| dispatch(addNewMessage({ | |||
| _id: data.chatId, | |||
| message: data.message | |||
| })) | |||
| }); | |||
| return () => removeMessageListener(); | |||
| }, []) | |||
| useEffect(() => { | |||
| setSortOption(sorting.selectedSortOption); | |||
| }, [sorting.selectedSortOption]); | |||
| @@ -19,6 +19,7 @@ import selectedTheme from "../../../themes"; | |||
| import { useFormik } from "formik"; | |||
| import * as Yup from "yup"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| const FirstStepCreateReview = (props) => { | |||
| const offer = props.offer; | |||
| @@ -26,6 +27,7 @@ const FirstStepCreateReview = (props) => { | |||
| const { isMobile } = useIsMobile(); | |||
| const { t } = useTranslation(); | |||
| const handleSubmit = (values) => { | |||
| console.log(values) | |||
| props.goToNextStep(values); | |||
| }; | |||
| @@ -64,7 +66,8 @@ const FirstStepCreateReview = (props) => { | |||
| > | |||
| <CreateReviewTitle>{t("reviews.modalTitle")}</CreateReviewTitle> | |||
| <ProfileImageContainer> | |||
| <ProfileImage src={interlocutor.image} /> | |||
| {/* <ProfileImage src={interlocutor.image} /> */} | |||
| <ProfileImage src={getImageUrl(interlocutor.image, variants.createReviewCard, false)} /> | |||
| </ProfileImageContainer> | |||
| <ProfileName>{interlocutor.name}</ProfileName> | |||
| <LittleOfferCard | |||
| @@ -14,6 +14,7 @@ const SecondStepCreateReview = (props) => { | |||
| const goToNextStep = () => { | |||
| props.goToNextStep(); | |||
| } | |||
| console.log(props.review); | |||
| return ( | |||
| <SecondStepCreateReviewContainer> | |||
| @@ -5,21 +5,34 @@ import DirectChatHeaderTitle from "./DirectChatHeaderTitle/DirectChatHeaderTitle | |||
| import DirectChatHeader from "./DirectChatHeader/DirectChatHeader"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { useLocation, useRouteMatch } from "react-router-dom"; | |||
| import { fetchOneChat, setOneChat } from "../../store/actions/chat/chatActions"; | |||
| import { selectSelectedChat } from "../../store/selectors/chatSelectors"; | |||
| import { | |||
| addNewMessage, | |||
| fetchChats, | |||
| fetchOneChat, | |||
| setOneChat, | |||
| } from "../../store/actions/chat/chatActions"; | |||
| import { | |||
| selectLatestChats, | |||
| selectSelectedChat, | |||
| } from "../../store/selectors/chatSelectors"; | |||
| import DirectChatContent from "./DirectChatContent/DirectChatContent"; | |||
| import { selectOffer } from "../../store/selectors/offersSelectors"; | |||
| import { fetchOneOffer } from "../../store/actions/offers/offersActions"; | |||
| import SkeletonDirectChat from "./SkeletonDirectChat/SkeletonDirectChat"; | |||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||
| import { CHAT_SCOPE } from "../../store/actions/chat/chatActionConstants"; | |||
| import { selectUserId } from "../../store/selectors/loginSelectors"; | |||
| import { addMesageListener, removeMessageListener } from "../../socket/socket"; | |||
| const DirectChat = () => { | |||
| const chat = useSelector(selectSelectedChat); | |||
| const allChats = useSelector(selectLatestChats); | |||
| const offer = useSelector(selectOffer); | |||
| const routeMatch = useRouteMatch(); | |||
| const location = useLocation(); | |||
| const dispatch = useDispatch(); | |||
| const userId = useSelector(selectUserId); | |||
| const isLoadingDirectChat = useSelector( | |||
| selectIsLoadingByActionType(CHAT_SCOPE) | |||
| ); | |||
| @@ -35,7 +48,7 @@ const DirectChat = () => { | |||
| if (location?.state?.offerId) { | |||
| return {}; | |||
| } | |||
| return chat?.chat; | |||
| return chat; | |||
| }, [chat, location.state]); | |||
| const interlocutorObject = useMemo(() => { | |||
| @@ -44,9 +57,16 @@ const DirectChat = () => { | |||
| image: offer?.companyData?.image, | |||
| name: offer?.companyData?.company?.name, | |||
| location: offer?.companyData?.company?.contacts?.location, | |||
| userId: offer?.offer?.userId, | |||
| }; | |||
| } | |||
| return chat?.interlocutor; | |||
| return { | |||
| ...chat?.interlocutor, | |||
| userId: | |||
| chat?.chat?.participants[0] === userId | |||
| ? chat?.chat?.participants[1] | |||
| : chat?.chat?.participants[0], | |||
| }; | |||
| }, [chat, location.state, offer]); | |||
| useEffect(() => { | |||
| @@ -55,6 +75,28 @@ const DirectChat = () => { | |||
| } | |||
| }, [routeMatch.params.idChat, location.state?.offerId]); | |||
| useEffect(() => { | |||
| addMesageListener((data) => { | |||
| if ( | |||
| [...allChats].find((item) => { | |||
| console.log("item.chat._id", item.chat._id); | |||
| console.log("data.chatId", data.chatId); | |||
| return item.chat._id === data.chatId; | |||
| }) | |||
| ) { | |||
| dispatch( | |||
| addNewMessage({ | |||
| _id: data.chatId, | |||
| message: data.message, | |||
| }) | |||
| ); | |||
| } else { | |||
| dispatch(fetchChats()); | |||
| } | |||
| }); | |||
| return () => removeMessageListener(); | |||
| }, [allChats]); | |||
| const refreshChat = () => { | |||
| if (routeMatch.params.idChat === "newMessage") { | |||
| dispatch(fetchOneOffer(location.state.offerId)); | |||
| @@ -16,7 +16,7 @@ import { selectIsLoadingByActionType } from "../../../store/selectors/loadingSel | |||
| import { CHAT_SCOPE } from "../../../store/actions/chat/chatActionConstants"; | |||
| const DirectChatContent = (props) => { | |||
| const messages = props?.chat?.messages; | |||
| const messages = props?.chat?.chat?.messages; | |||
| const userId = useSelector(selectUserId); | |||
| const myProfileImage = useSelector(selectMineProfilePicture); | |||
| const messagesRef = useRef(null); | |||
| @@ -28,9 +28,9 @@ const DirectChatContent = (props) => { | |||
| props.refreshChat(); | |||
| }; | |||
| useEffect(() => { | |||
| const offsetBottom = | |||
| messagesRef.current?.offsetTop + messagesRef.current?.offsetHeight; | |||
| messagesRef.current?.scrollTo({ top: offsetBottom, behaviour: "smooth" }); | |||
| // const offsetBottom = | |||
| // messagesRef.current?.offsetTop + messagesRef.current?.offsetHeight; | |||
| messagesRef.current?.scrollTo({ top: messagesRef.current.scrollHeight, behaviour: "smooth" }); | |||
| }, [messages]); | |||
| return ( | |||
| <> | |||
| @@ -46,7 +46,7 @@ const DirectChatContent = (props) => { | |||
| ? myProfileImage | |||
| : interlucatorProfileImage; | |||
| return ( | |||
| <MessageContainer key={item?._id}> | |||
| <MessageContainer key={item?._id || item?._created}> | |||
| <MessageCard | |||
| message={item} | |||
| image={image} | |||
| @@ -57,8 +57,10 @@ const DirectChatContent = (props) => { | |||
| })} | |||
| </MessagesList> | |||
| <DirectChatNewMessage | |||
| chatId={props?.chat?._id} | |||
| chat={props?.chat} | |||
| chatId={props?.chat?.chat?._id} | |||
| refreshChat={handleRefresh} | |||
| interlucator={props.interlucator} | |||
| /> | |||
| </DirectChatContentContainer> | |||
| )} | |||
| @@ -14,22 +14,30 @@ import { | |||
| } from "./DirectChatContentHeader.styled"; | |||
| import PopoverComponent from "../../../Popovers/PopoverComponent"; | |||
| import PhonePopover from "../../../Popovers/PhonePopover/PhonePopover"; | |||
| import { getImageUrl, variants } from "../../../../util/helpers/imageUrlGetter"; | |||
| import useIsMobile from "../../../../hooks/useIsMobile"; | |||
| const DirectChatContentHeader = (props) => { | |||
| const [showPhonePopover, setShowPhonePopover] = useState(false); | |||
| const [phonePopoverAnchorEl, setPhonePopoverAnchorEl] = useState(null); | |||
| const { isMobile } = useIsMobile(); | |||
| const togglePhonePopover = (event) => { | |||
| console.log(event); | |||
| setShowPhonePopover(true); | |||
| setPhonePopoverAnchorEl(event.currentTarget); | |||
| } | |||
| setShowPhonePopover(true); | |||
| setPhonePopoverAnchorEl(event.currentTarget); | |||
| }; | |||
| return ( | |||
| <DirectChatContentHeaderContainer> | |||
| <DirectChatContentHeaderFlexContainer> | |||
| <ProfileImage src={props?.interlucator?.image} /> | |||
| {/* <ProfileImage src={props?.interlucator?.image} /> */} | |||
| <ProfileImage | |||
| src={getImageUrl( | |||
| props?.interlucator?.image, | |||
| variants.chatHeader, | |||
| isMobile | |||
| )} | |||
| /> | |||
| <ProfileDetails> | |||
| <ProfileName>{props?.interlucator?.name}</ProfileName> | |||
| <ProfileLocation> | |||
| @@ -8,40 +8,106 @@ import { | |||
| import { useTranslation } from "react-i18next"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useDispatch } from "react-redux"; | |||
| // import { | |||
| // fetchChats, | |||
| // startNewChat, | |||
| // } from "../../../store/actions/chat/chatActions"; | |||
| // import { useHistory, useLocation } from "react-router-dom"; | |||
| import { sendMessage } from "../../../socket/socket"; | |||
| import { useSelector } from "react-redux"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import { | |||
| fetchChats, | |||
| sendMessage, | |||
| addNewMessage, | |||
| // fetchChats, | |||
| // fetchOneChat, | |||
| startNewChat, | |||
| } from "../../../store/actions/chat/chatActions"; | |||
| import { useHistory, useLocation } from "react-router-dom"; | |||
| import { selectExchange } from "../../../store/selectors/exchangeSelector"; | |||
| import { validateExchange } from "../../../store/actions/exchange/exchangeActions"; | |||
| // import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| const DirectChatNewMessage = (props) => { | |||
| const [typedValue, setTypedValue] = useState(""); | |||
| const [isFocused, setIsFocused] = useState(false); | |||
| const exchange = useSelector(selectExchange); | |||
| const dispatch = useDispatch(); | |||
| const { t } = useTranslation(); | |||
| const location = useLocation(); | |||
| const history = useHistory(); | |||
| const handleApiResponseSuccess = () => { | |||
| props.refreshChat(); | |||
| }; | |||
| const handleSend = useCallback(() => { | |||
| if (location.state?.offerId) { | |||
| initiateNewChat(typedValue); | |||
| } else { | |||
| dispatch( | |||
| sendMessage({ | |||
| message: typedValue, | |||
| chatId: props.chatId, | |||
| handleApiResponseSuccess, | |||
| }) | |||
| ); | |||
| } | |||
| setTypedValue(""); | |||
| }, [typedValue]); | |||
| // const handleApiResponseSuccess = () => { | |||
| // props.refreshChat(); | |||
| // }; | |||
| const userId = useSelector(selectUserId); | |||
| // useEffect(() => { | |||
| // if (props.chatId) { | |||
| // dispatch(fetchOneChat(props.chatId)); | |||
| // console.log("fetchuje se") | |||
| // } | |||
| // }, [props.chatId]); | |||
| const handleSend = useCallback( | |||
| (newChatId = undefined) => { | |||
| // if (location.state?.offerId) { | |||
| // initiateNewChat(typedValue); | |||
| // } else { | |||
| // dispatch( | |||
| // sendMessage({ | |||
| // message: typedValue, | |||
| // chatId: props.chatId, | |||
| // handleApiResponseSuccess, | |||
| // }) | |||
| // ); | |||
| // } | |||
| if (props.chatId || newChatId) { | |||
| const chatId = props.chatId || newChatId; | |||
| sendMessage(chatId, userId, typedValue, props.interlucator.userId); | |||
| dispatch( | |||
| addNewMessage({ | |||
| _id: chatId, | |||
| message: { | |||
| userId, | |||
| text: typedValue, | |||
| _created: new Date().toISOString(), | |||
| }, | |||
| }) | |||
| ); | |||
| if (props.chatId) { | |||
| if (!exchange.valid && props.chat?.offer?.offer?.userId === userId) { | |||
| dispatch(validateExchange(exchange._id)); | |||
| } | |||
| } | |||
| } else { | |||
| initiateNewChat(typedValue); | |||
| } | |||
| // socket.emit("private_message", { | |||
| // chatId: props.chatId, | |||
| // receiverUserId: props.interlucator.userId, | |||
| // message: typedValue | |||
| // // message: { | |||
| // // userId: userId, | |||
| // // text: typedValue, | |||
| // // receiverUserId: props.interlucator.userId | |||
| // // } | |||
| // }) | |||
| setTypedValue(""); | |||
| }, | |||
| [typedValue, props.chatId, userId, props.interlucator.userId] | |||
| ); | |||
| const handleMessageSendSuccess = (newChatId) => { | |||
| history.replace(`${newChatId}`); | |||
| dispatch(fetchChats()); | |||
| // dispatch(fetchChats()); | |||
| // sendMessage(newChatId, userId, typedValue, props.interlucator.userId); | |||
| // dispatch( | |||
| // addNewMessage({ | |||
| // _id: newChatId, | |||
| // message: { | |||
| // userId, | |||
| // text: typedValue, | |||
| // _created: new Date().toISOString(), | |||
| // }, | |||
| // }) | |||
| // ); | |||
| // handleSend(newChatId); | |||
| }; | |||
| useEffect(() => { | |||
| @@ -55,7 +121,12 @@ const DirectChatNewMessage = (props) => { | |||
| const initiateNewChat = (typedValue) => { | |||
| const offerId = location.state.offerId; | |||
| dispatch( | |||
| startNewChat({ offerId, message: typedValue, handleMessageSendSuccess }) | |||
| startNewChat({ | |||
| offerId, | |||
| message: typedValue, | |||
| interlucatorUserId: props.interlucator.userId, | |||
| handleMessageSendSuccess, | |||
| }) | |||
| ); | |||
| }; | |||
| return ( | |||
| @@ -84,6 +155,8 @@ DirectChatNewMessage.propTypes = { | |||
| children: PropTypes.node, | |||
| chatId: PropTypes.any, | |||
| refreshChat: PropTypes.func, | |||
| interlucator: PropTypes.any, | |||
| chat: PropTypes.any, | |||
| }; | |||
| export default DirectChatNewMessage; | |||
| @@ -8,7 +8,6 @@ import { selectAboutRouteSelected } from "../../../store/selectors/appSelectors" | |||
| import { useHistory } from "react-router-dom"; | |||
| const AboutHeader = () => { | |||
| console.log("about header"); | |||
| const history = useHistory(); | |||
| const { t } = useTranslation(); | |||
| const aboutRouteSelected = useSelector(selectAboutRouteSelected); | |||
| @@ -313,7 +313,7 @@ const Header = () => { | |||
| color: selectedTheme.primaryPurple, | |||
| }} | |||
| > | |||
| <Badge badgeContent={3} color="primary"> | |||
| <Badge color="primary"> | |||
| <MailIcon /> | |||
| </Badge> | |||
| </IconButton> | |||
| @@ -11,6 +11,7 @@ import { | |||
| import { IconButton } from "../Buttons/IconButton/IconButton"; | |||
| import { ReactComponent as EditIcon } from "../../assets/images/svg/edit.svg"; | |||
| import { ReactComponent as TrashIcon } from "../../assets/images/svg/trash.svg"; | |||
| import { getImageUrl, variants } from "../../util/helpers/imageUrlGetter"; | |||
| const ImagePicker = (props) => { | |||
| const fileInputRef = useRef(null); | |||
| @@ -19,19 +20,27 @@ const ImagePicker = (props) => { | |||
| const [isEditing, setIsEditing] = useState(false); | |||
| useEffect(() => { | |||
| if (props.image) | |||
| setImage(props.image); | |||
| }, [props.image]); | |||
| let listener = useCallback((event) => { | |||
| if (imageRef.current) { | |||
| if (imageRef.current.contains(event.target)) { | |||
| setIsEditing(true); | |||
| if (props.image) { | |||
| if (typeof props.image === 'string') { | |||
| setImage(getImageUrl(props.image, variants.offerCard)) | |||
| } else { | |||
| setIsEditing(false); | |||
| handleImage(props.image); | |||
| } | |||
| } | |||
| }, [imageRef.current]) | |||
| }, [props.image]); | |||
| let listener = useCallback( | |||
| (event) => { | |||
| if (imageRef.current) { | |||
| if (imageRef.current.contains(event.target)) { | |||
| setIsEditing(true); | |||
| } else { | |||
| setIsEditing(false); | |||
| } | |||
| } | |||
| }, | |||
| [imageRef.current] | |||
| ); | |||
| useEffect(() => { | |||
| window.addEventListener("click", listener); | |||
| return () => window.removeEventListener("click", listener); | |||
| @@ -40,11 +49,12 @@ const ImagePicker = (props) => { | |||
| fileInputRef.current.value = ""; | |||
| fileInputRef.current.click(); | |||
| }; | |||
| const handleImage = (event) => { | |||
| const handleImage = (file) => { | |||
| let reader = new FileReader(); | |||
| reader.readAsDataURL(event.target.files[0]); | |||
| reader.readAsDataURL(file); | |||
| // reader.readAsBinaryString(file); | |||
| reader.onload = () => { | |||
| if (props.setImage) props.setImage(reader.result); | |||
| if (props.setImage) props.setImage(file); | |||
| setImage(reader.result); | |||
| }; | |||
| reader.onerror = (error) => { | |||
| @@ -61,8 +71,15 @@ const ImagePicker = (props) => { | |||
| className={props.className} | |||
| onClick={!image ? handleChange : () => {}} | |||
| hasImage={props.image} | |||
| component="form" | |||
| > | |||
| <AddFile type="file" ref={fileInputRef} onInput={handleImage} accept=".jpg, .jpeg, .png" /> | |||
| <AddFile | |||
| type="file" | |||
| ref={fileInputRef} | |||
| onInput={(event) => handleImage(event.target.files[0])} | |||
| accept=".jpg, .jpeg, .png" | |||
| formEncType="multipart/form-data" | |||
| /> | |||
| {image ? ( | |||
| <React.Fragment> | |||
| <ImageUploaded src={image} draggable={false} ref={imageRef} /> | |||
| @@ -18,12 +18,15 @@ import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import StatisticDetails from "./StatisticDetails/StatisticDetails"; | |||
| import PIBDetail from "./OfferDetail/PIB/PIBDetail"; | |||
| import CategoryDetail from "./OfferDetail/Category/CategoryDetail"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| const ItemDetailsHeaderCard = (props) => { | |||
| const history = useHistory(); | |||
| const chats = useSelector(selectLatestChats); | |||
| const offer = props.offer; | |||
| const userId = useSelector(selectUserId); | |||
| const { isMobile } = useIsMobile(); | |||
| const handleGoProfile = () => { | |||
| history.push(`/profile/${offer?.offer?.userId}`); | |||
| @@ -48,7 +51,14 @@ const ItemDetailsHeaderCard = (props) => { | |||
| halfwidth={props.halfwidth ? 1 : 0} | |||
| > | |||
| <HeaderTop> | |||
| <OfferImage src={offer?.companyData?.image} /> | |||
| {/* <OfferImage src={offer?.companyData?.image} /> */} | |||
| <OfferImage | |||
| src={getImageUrl( | |||
| offer?.companyData?.image, | |||
| variants.profileImage, | |||
| isMobile | |||
| )} | |||
| /> | |||
| <OfferDetails> | |||
| <OfferTitle isMyProfile={props.isMyProfile} onClick={handleGoProfile}> | |||
| {offer?.companyData?.company?.name} | |||
| @@ -15,7 +15,7 @@ const CategoryDetail = (props) => { | |||
| <DetailIcon color={selectedTheme.iconStrokeColor} size="22px"> | |||
| <Category width={"22px"} /> | |||
| </DetailIcon> | |||
| <DetailText isMyProfile={props.isMyProfile}> | |||
| <DetailText ismyprofile={props.isMyProfile}> | |||
| {offer?.companyData?.company?.contacts?.location} | |||
| </DetailText> | |||
| </DetailContainer> | |||
| @@ -27,7 +27,7 @@ export const DetailIcon = styled(Icon)` | |||
| `; | |||
| export const DetailText = styled(Typography)` | |||
| font-family: "DM Sans"; | |||
| color: ${props => props.isMyProfile ? "white" : selectedTheme.primaryText}; | |||
| color: ${props => props.ismyprofile ? "white" : selectedTheme.primaryText}; | |||
| line-height: 16px; | |||
| font-size: 16px; | |||
| position: relative; | |||
| @@ -62,9 +62,7 @@ const Login = () => { | |||
| const handleSubmit = (values) => { | |||
| const { email, password } = values; | |||
| if (!formik.isValid) { | |||
| console.log("invalid"); | |||
| } else { | |||
| if (formik.isValid) { | |||
| dispatch(clearLoginErrors()); | |||
| dispatch( | |||
| fetchLogin({ | |||
| @@ -74,7 +72,6 @@ const Login = () => { | |||
| handleApiResponseError, | |||
| }) | |||
| ); | |||
| console.log(values); | |||
| } | |||
| }; | |||
| @@ -10,6 +10,7 @@ export const OffersNotFoundContainer = styled(Box)` | |||
| align-items: center; | |||
| justify-content: center; | |||
| height: 70vh; | |||
| text-align: center; | |||
| `; | |||
| export const OffersNotFoundHeading = styled(Typography)` | |||
| @@ -17,45 +17,60 @@ import { | |||
| SecondaryTextContainer, | |||
| } from "./HeaderPopover.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| import { useMemo } from "react"; | |||
| const HeaderPopover = (props) => { | |||
| const { t } = useTranslation(); | |||
| const { isMobile } = useIsMobile(); | |||
| const items = useMemo(() => props.items, [props.items]); | |||
| return ( | |||
| <HeaderPopoverContainer> | |||
| <PopoverTitle p={2}>{props.title}</PopoverTitle> | |||
| <PopoverList> | |||
| {props.items?.length > 0 ? ( | |||
| props.items.map((item, index) => ( | |||
| <PopoverListItem key={index}> | |||
| <PopoverListItemAvatarContainer> | |||
| {props.isProfile ? ( | |||
| <PopoverListItemProfileAvatar | |||
| alt={item.alt} | |||
| src={item.src} | |||
| onClick={item?.onClick} | |||
| /> | |||
| ) : ( | |||
| <PopoverListItemAvatar | |||
| alt={item.alt} | |||
| src={item.src} | |||
| onClick={item?.onClick} | |||
| /> | |||
| )} | |||
| </PopoverListItemAvatarContainer> | |||
| <PopoverListItemTextContainer | |||
| primaryTypographyProps={{ | |||
| onClick: item.onClick, | |||
| }} | |||
| primary={item.title} | |||
| secondary={ | |||
| <SecondaryTextContainer> | |||
| <SecondaryText>{item.text}</SecondaryText> | |||
| <NameOfProduct>{item?.bigText}</NameOfProduct> | |||
| </SecondaryTextContainer> | |||
| } | |||
| ></PopoverListItemTextContainer> | |||
| </PopoverListItem> | |||
| )) | |||
| {items?.length > 0 ? ( | |||
| items.map((item, index) => { | |||
| return ( | |||
| <PopoverListItem key={index}> | |||
| <PopoverListItemAvatarContainer> | |||
| {props.isProfile ? ( | |||
| <PopoverListItemProfileAvatar | |||
| alt={item.alt} | |||
| src={getImageUrl( | |||
| item.src, | |||
| variants.profileCard, | |||
| isMobile | |||
| )} | |||
| onClick={item?.onClick} | |||
| /> | |||
| ) : ( | |||
| <PopoverListItemAvatar | |||
| alt={item.alt} | |||
| src={getImageUrl( | |||
| item.src, | |||
| variants.profileCard, | |||
| isMobile | |||
| )} | |||
| onClick={item?.onClick} | |||
| /> | |||
| )} | |||
| </PopoverListItemAvatarContainer> | |||
| <PopoverListItemTextContainer | |||
| primaryTypographyProps={{ | |||
| onClick: item.onClick, | |||
| }} | |||
| primary={item.title} | |||
| secondary={ | |||
| <SecondaryTextContainer> | |||
| <SecondaryText>{item.text}</SecondaryText> | |||
| <NameOfProduct>{item?.bigText}</NameOfProduct> | |||
| </SecondaryTextContainer> | |||
| } | |||
| ></PopoverListItemTextContainer> | |||
| </PopoverListItem> | |||
| ); | |||
| }) | |||
| ) : ( | |||
| <PopoverNoItemsText>{t("header.noItems")}</PopoverNoItemsText> | |||
| )} | |||
| @@ -7,6 +7,7 @@ import { selectLatestChats } from "../../../store/selectors/chatSelectors"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import HeaderPopover from "../HeaderPopover/HeaderPopover"; | |||
| import PropTypes from "prop-types"; | |||
| import { makeErrorToastMessage } from "../../../store/utils/makeToastMessage"; | |||
| export const MyMessages = (props) => { | |||
| const { t } = useTranslation(); | |||
| @@ -17,14 +18,13 @@ export const MyMessages = (props) => { | |||
| const [lastChats, setLastChats] = useState([]); | |||
| const convertMessages = (messages) => { | |||
| console.log(messages) | |||
| return messages | |||
| .map((item) => ({ | |||
| src: item.interlocutorData.image, | |||
| title: item.interlocutorData.name, | |||
| onClick: () => goToMessage(item?.chat?._id), | |||
| text: "Proizvod: ", | |||
| bigText: item.offerData.name | |||
| bigText: item.offerData.name, | |||
| })) | |||
| .slice(0, 2); | |||
| }; | |||
| @@ -40,8 +40,13 @@ export const MyMessages = (props) => { | |||
| } | |||
| }, [chats]); | |||
| const goToMessages = () => { | |||
| history.push(`/messages/${chats[0].chat?._id}`); | |||
| props.closePopover(); | |||
| if (lastChats.length !== 0) { | |||
| history.push(`/messages/${chats[0].chat?._id}`); | |||
| props.closePopover(); | |||
| } else { | |||
| makeErrorToastMessage(t("messages.noMessagesToast")); | |||
| props.closePopover(); | |||
| } | |||
| }; | |||
| const goToMessage = (chatId) => { | |||
| history.push(`/messages/${chatId}`); | |||
| @@ -24,6 +24,7 @@ import { fetchMineOffers } from "../../../store/actions/offers/offersActions"; | |||
| import { selectProfileName } from "../../../store/selectors/profileSelectors"; | |||
| import { useHistory } from "react-router-dom"; | |||
| import { MY_OFFERS_PAGE } from "../../../constants/pages"; | |||
| import { useMemo } from "react"; | |||
| export const MyPosts = (props) => { | |||
| const { t } = useTranslation(); | |||
| @@ -36,40 +37,42 @@ export const MyPosts = (props) => { | |||
| dispatch(fetchMineOffers()); | |||
| }, []); | |||
| useEffect(() => { | |||
| if (mineOffers?.length > 0) { | |||
| if (mineOffers.length > 1) { | |||
| setArrayOfMineOffers( | |||
| [mineOffers[0], mineOffers[1]].map((item) => ({ | |||
| src: item.images[0], | |||
| title: item.name, | |||
| onClick: () => goToOffer(item._id), | |||
| text: ( | |||
| <React.Fragment> | |||
| <PostsImgSuit /> {name} | |||
| </React.Fragment> | |||
| ), | |||
| })) | |||
| ); | |||
| } else if (mineOffers.length > 0) { | |||
| setArrayOfMineOffers( | |||
| [mineOffers[0]].map((item) => ({ | |||
| alt: "Photo", | |||
| src: item.images[0], | |||
| title: item.name, | |||
| onClick: () => goToOffer(item._id), | |||
| text: ( | |||
| <React.Fragment> | |||
| <PostsImgSuit /> {name} | |||
| </React.Fragment> | |||
| ), | |||
| })) | |||
| ); | |||
| } else { | |||
| setArrayOfMineOffers([]); | |||
| } | |||
| if (mineOffers.length > 1) { | |||
| setArrayOfMineOffers( | |||
| [mineOffers[0], mineOffers[1]].map((item) => ({ | |||
| src: item.images[0], | |||
| title: item.name, | |||
| onClick: () => goToOffer(item._id), | |||
| text: ( | |||
| <React.Fragment> | |||
| <PostsImgSuit /> {name} | |||
| </React.Fragment> | |||
| ), | |||
| })) | |||
| ); | |||
| } else if (mineOffers.length > 0) { | |||
| setArrayOfMineOffers( | |||
| [mineOffers[0]].map((item) => ({ | |||
| alt: "Photo", | |||
| src: item.images[0], | |||
| title: item.name, | |||
| onClick: () => goToOffer(item._id), | |||
| text: ( | |||
| <React.Fragment> | |||
| <PostsImgSuit /> {name} | |||
| </React.Fragment> | |||
| ), | |||
| })) | |||
| ); | |||
| } else { | |||
| setArrayOfMineOffers([]); | |||
| } | |||
| }, [mineOffers]); | |||
| const mineOffersToRender = useMemo(() => { | |||
| return [...arrayOfMineOffers]; | |||
| }, [arrayOfMineOffers, mineOffers]); | |||
| const goToOffer = (id) => { | |||
| history.push(`/proizvodi/${id}`); | |||
| props.closePopover(); | |||
| @@ -81,7 +84,7 @@ export const MyPosts = (props) => { | |||
| return ( | |||
| <HeaderPopover | |||
| title={t("header.myOffers")} | |||
| items={arrayOfMineOffers} | |||
| items={mineOffersToRender} | |||
| buttonText={t("header.checkEverything")} | |||
| buttonOnClick={goToMySwaps} | |||
| /> | |||
| @@ -37,7 +37,6 @@ const ProfileOffers = (props) => { | |||
| const isLoadingMineOffers = useSelector( | |||
| selectIsLoadingByActionType(OFFERS_PROFILE_SCOPE) | |||
| ); | |||
| console.log("isLoadingMineOffers", isLoadingMineOffers); | |||
| const searchRef = useRef(null); | |||
| const chats = useSelector(selectLatestChats); | |||
| const profileOffers = useSelector(selectProfileOffers); | |||
| @@ -15,6 +15,7 @@ import { | |||
| ButtonsContainer, | |||
| ErrorMessage, | |||
| ProfileImagePicker, | |||
| InputFieldLabelLocation, | |||
| } from "./EditProfile.styled"; | |||
| import selectedTheme from "../../../themes"; | |||
| import { useFormik } from "formik"; | |||
| @@ -29,6 +30,8 @@ import { useDispatch, useSelector } from "react-redux"; | |||
| import { selectUserId } from "../../../store/selectors/loginSelectors"; | |||
| import editProfileValidation from "../../../validations/editProfileValidation"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| import AutoSuggestTextField from "../../TextFields/AutoSuggestTextField/AutoSuggestTextField"; | |||
| import { selectLocations } from "../../../store/selectors/locationsSelectors"; | |||
| const EditProfile = (props) => { | |||
| const [profileImage, setProfileImage] = useState(props.profile.image); | |||
| @@ -38,6 +41,7 @@ const EditProfile = (props) => { | |||
| const dispatch = useDispatch(); | |||
| const { isMobile } = useIsMobile(); | |||
| const userId = useSelector(selectUserId); | |||
| const locations = useSelector(selectLocations); | |||
| useEffect(() => { | |||
| if (isMobile) { | |||
| @@ -53,7 +57,6 @@ const EditProfile = (props) => { | |||
| }; | |||
| const handleSubmit = (values) => { | |||
| console.log(values); | |||
| dispatch(editMineProfile({ ...values, handleApiResponseSuccess })); | |||
| props.closeModalHandler(); | |||
| }; | |||
| @@ -132,23 +135,32 @@ const EditProfile = (props) => { | |||
| fullWidth | |||
| disabled | |||
| /> | |||
| <InputFieldLabel | |||
| <InputFieldLabelLocation | |||
| leftText={t("common.labelLocation").toUpperCase()} | |||
| /> | |||
| <InputField | |||
| <AutoSuggestTextField | |||
| editLocation | |||
| data={locations.map((item) => ({ name: item.city }))} | |||
| value={formik.values.firmLocation} | |||
| onChange={(event, { newValue }) => | |||
| formik.setFieldValue("firmLocation", newValue) | |||
| } | |||
| /> | |||
| {/* <InputField | |||
| name="firmLocation" | |||
| value={formik.values.firmLocation} | |||
| onChange={formik.handleChange} | |||
| error={formik.touched.firmLocation && formik.errors.firmLocation} | |||
| margin="normal" | |||
| fullWidth | |||
| /> | |||
| /> */} | |||
| </BasicInfo> | |||
| )} | |||
| {showDetails && ( | |||
| <DetailsInfo> | |||
| <InputFieldLabel | |||
| leftText={t("editProfile.website").toUpperCase()} | |||
| labelWebsite | |||
| /> | |||
| <InputField | |||
| name="firmWebsite" | |||
| @@ -86,6 +86,7 @@ export const CloseButton = styled(Box)` | |||
| export const InputFieldLabel = styled(Label)` | |||
| position: relative; | |||
| bottom: -14px; | |||
| & label { | |||
| font-size: 12px; | |||
| font-weight: 600; | |||
| @@ -93,12 +94,13 @@ export const InputFieldLabel = styled(Label)` | |||
| color: #808080; | |||
| cursor: auto; | |||
| letter-spacing: 0.2px; | |||
| ${(props) => props.labelWebsite && `margin-top: 8px`} | |||
| } | |||
| @media screen and (max-width: 600px) { | |||
| & label { | |||
| font-size: 9px; | |||
| margin-top: -3px; | |||
| margin-top: 0; | |||
| } | |||
| } | |||
| `; | |||
| @@ -117,6 +119,29 @@ export const InputField = styled(TextField)` | |||
| } | |||
| `; | |||
| export const InputFieldLabelLocation = styled(Label)` | |||
| position: relative; | |||
| bottom: -8px; | |||
| margin: 10px 0; | |||
| & label { | |||
| font-size: 12px; | |||
| font-weight: 600; | |||
| line-height: 20px; | |||
| color: #808080; | |||
| cursor: auto; | |||
| letter-spacing: 0.2px; | |||
| } | |||
| @media screen and (max-width: 600px) { | |||
| bottom: -12px; | |||
| margin: 5px 0 17px 0; | |||
| & label { | |||
| font-size: 9px; | |||
| margin-top: 0; | |||
| } | |||
| } | |||
| `; | |||
| export const SaveButton = styled(PrimaryButton)` | |||
| font-size: 12px; | |||
| letter-spacing: 1.5px; | |||
| @@ -11,15 +11,25 @@ import { | |||
| ProfilePIB, | |||
| } from "./ProfileMainInfo.styled"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { getImageUrl, variants } from "../../../util/helpers/imageUrlGetter"; | |||
| import useIsMobile from "../../../hooks/useIsMobile"; | |||
| const ProfileMainInfo = (props) => { | |||
| const { t } = useTranslation(); | |||
| const { isMobile } = useIsMobile(); | |||
| return ( | |||
| <ProfileMainInfoContainer> | |||
| <AvatarImageContainer> | |||
| <AvatarImage | |||
| {/* <AvatarImage | |||
| alt={props.profile?.company?.name} | |||
| src={props.profile?.image} | |||
| /> */} | |||
| <AvatarImage | |||
| src={getImageUrl( | |||
| props.profile?.image, | |||
| variants.profileImage, | |||
| isMobile | |||
| )} | |||
| /> | |||
| </AvatarImageContainer> | |||
| <ProfileMainInfoGrid> | |||
| @@ -12,18 +12,18 @@ const AutoSuggestTextField = (props) => { | |||
| const getSuggestions = (value) => { | |||
| const escapedValue = escapeRegexCharacters(value.trim()); | |||
| if (escapedValue === "") { | |||
| return []; | |||
| } | |||
| const regex = new RegExp("^" + escapedValue, "i"); | |||
| const suggestions = data.filter((dataItem) => regex.test(dataItem.name)); | |||
| if (suggestions.length === 0) { | |||
| return [{ isAddNew: true }]; | |||
| } | |||
| return suggestions; | |||
| }; | |||
| @@ -49,7 +49,7 @@ const AutoSuggestTextField = (props) => { | |||
| }; | |||
| return ( | |||
| <AutoSuggestTextFieldContainer> | |||
| <AutoSuggestTextFieldContainer editLocation={props.editLocation}> | |||
| <Autosuggest | |||
| suggestions={suggestions} | |||
| onSuggestionsFetchRequested={onSuggestionsFetchRequested} | |||
| @@ -68,6 +68,7 @@ AutoSuggestTextField.propTypes = { | |||
| value: PropTypes.string, | |||
| onChange: PropTypes.func, | |||
| data: PropTypes.array, | |||
| editLocation: PropTypes.bool, | |||
| }; | |||
| export default AutoSuggestTextField; | |||
| @@ -5,12 +5,12 @@ import selectedTheme from "../../../themes"; | |||
| export const AutoSuggestTextFieldContainer = styled(Box)` | |||
| & .react-autosuggest__container { | |||
| width: 100%; | |||
| height: 48px; | |||
| height: ${(props) => (props.editLocation ? "43px" : "48px")}; | |||
| position: relative; | |||
| z-index: 20; | |||
| & input { | |||
| width: 100%; | |||
| height: 48px; | |||
| height: ${(props) => (props.editLocation ? "43px" : "48px")}; | |||
| padding: 4px 14px; | |||
| border-radius: 4px; | |||
| border: 1px solid rgba(0, 0, 0, 0.23); | |||
| @@ -27,7 +27,7 @@ export const AutoSuggestTextFieldContainer = styled(Box)` | |||
| border: 2px solid ${selectedTheme.primaryPurple}; | |||
| } | |||
| & input::placeholder { | |||
| color: rgba(0,0,0, 0.38); | |||
| color: rgba(0, 0, 0, 0.38); | |||
| } | |||
| & div { | |||
| @@ -56,4 +56,12 @@ export const AutoSuggestTextFieldContainer = styled(Box)` | |||
| color: ${selectedTheme.primaryBackgroundColor}; | |||
| } | |||
| } | |||
| @media (max-width: 600px) { | |||
| & .react-autosuggest__container { | |||
| & input { | |||
| font-size: 13px; | |||
| } | |||
| } | |||
| } | |||
| `; | |||
| @@ -31,7 +31,6 @@ const UserReviews = (props) => { | |||
| const dispatch = useDispatch(); | |||
| useEffect(() => { | |||
| console.log(routeMatch); | |||
| if (props.profileReviews && routeMatch.params?.idProfile) { | |||
| let idProfile = routeMatch.params.idProfile; | |||
| dispatch(fetchReviews(idProfile)); | |||
| @@ -33,7 +33,6 @@ const useSearch = (applyAllFilters) => { | |||
| clear(); | |||
| } | |||
| const queryObject = new URLSearchParams(history.location.search); | |||
| console.log(queryObject.toString()) | |||
| if (queryObject.has(KEY_SEARCH)) { | |||
| searchOffers(queryObject.get(KEY_SEARCH)); | |||
| } else { | |||
| @@ -43,7 +42,6 @@ const useSearch = (applyAllFilters) => { | |||
| // On every local change of search string, global state of search string should be also updated | |||
| useEffect(() => { | |||
| console.log('ovde') | |||
| if (isInitallyLoaded && applyAllFilters) { | |||
| dispatch(setSearchString(searchStringLocally)); | |||
| } | |||
| @@ -55,7 +53,6 @@ const useSearch = (applyAllFilters) => { | |||
| }; | |||
| const clear = () => { | |||
| console.log('ovde2') | |||
| setSearchStringLocally(""); | |||
| }; | |||
| @@ -208,6 +208,7 @@ export default { | |||
| send: "Pošalji", | |||
| sendPlaceholder: "Poruka...", | |||
| seeChats: "Pogledaj ćaskanje", | |||
| noMessagesToast: "Nemate ni jednu poruku!" | |||
| }, | |||
| editProfile: { | |||
| website: "Web Sajt*", | |||
| @@ -40,14 +40,13 @@ const AboutPage = () => { | |||
| const yAxis = privacyPolicyRef.current.offsetTop - 64; | |||
| window.scrollTo({ top: yAxis, behavior: "smooth" }); | |||
| if (aboutRouteSelected !== scrollConstants.about.privacyPolicyPage) { | |||
| dispatch(setAboutRouteSelected(scrollConstants.about.privacyPolicyPage)); | |||
| dispatch( | |||
| setAboutRouteSelected(scrollConstants.about.privacyPolicyPage) | |||
| ); | |||
| } | |||
| } | |||
| location.state = {}; | |||
| } | |||
| return () => { | |||
| console.log(location); | |||
| }; | |||
| }, [location]); | |||
| useEffect(() => { | |||
| @@ -56,7 +55,6 @@ const AboutPage = () => { | |||
| window.scrollY > | |||
| pricesRef.current.offsetTop - window.innerHeight / 2 | |||
| ) { | |||
| console.log(true); | |||
| if ( | |||
| window.scrollY > | |||
| privacyPolicyRef.current.offsetTop - window.innerHeight / 2 | |||
| @@ -21,7 +21,6 @@ const ItemDetailsPage = (props) => { | |||
| }, []); | |||
| useEffect(() => { | |||
| console.log("effect: selectedOffer", selectedOffer, " isInitiallyLoaded: ", isInitiallyLoaded) | |||
| if (!selectedOffer?.offer && isInitiallyLoaded) { | |||
| dispatch(fetchOneOffer(offerId)); | |||
| } | |||
| @@ -40,7 +40,6 @@ const FirstPartOfRegistration = (props) => { | |||
| const handleSubmitForm = (event) => { | |||
| event.preventDefault(); | |||
| console.log(formik); | |||
| if (!formik.isValid) { | |||
| if (formik.errors.mail) { | |||
| formik.setFieldValue("mail", ""); | |||
| @@ -71,7 +71,6 @@ const Register = () => { | |||
| } | |||
| }; | |||
| const registerUser = (values) => { | |||
| dispatch( | |||
| fetchRegisterUser({ values, handleResponseSuccess, handleResponseError }) | |||
| @@ -0,0 +1,32 @@ | |||
| import io from "socket.io-client"; | |||
| export const socket = io("https://trampa-api-test.dilig.net/", { | |||
| autoConnect: true, | |||
| transports: ["websocket"], | |||
| reconnectionAttempts: 5, | |||
| }); | |||
| export const socketInit = (userId) => { | |||
| socket.auth = { | |||
| userId, | |||
| }; | |||
| }; | |||
| export const sendMessage = (chatId, userId, text, receiverUserId) => { | |||
| console.log("CHATID: ", chatId); | |||
| socket.emit("private_message", { | |||
| chatId, | |||
| receiverUserId, | |||
| message: { | |||
| userId, | |||
| text, | |||
| }, | |||
| }); | |||
| }; | |||
| export const addMesageListener = (listener) => { | |||
| return socket.on("private_message", listener); | |||
| }; | |||
| export const removeMessageListener = () => { | |||
| return socket.off("private_message"); | |||
| }; | |||
| @@ -34,4 +34,5 @@ export const CHAT_NEW_FETCH_ERROR = createErrorType(CHAT_NEW_SCOPE); | |||
| export const CHAT_SET = createSetType("CHAT_SET"); | |||
| export const CHAT_ONE_SET = createSetType("CHAT_ONE_SET"); | |||
| export const CHAT_CLEAR = createSetType("CHAT_CLEAR"); | |||
| export const CHAT_ADD_MESSAGE = createSetType("CHAT_ADD_MESSAGE"); | |||
| // export const ADD_ONE_CHAT = "CHAT_ONE_ADD"; | |||
| @@ -1,4 +1,4 @@ | |||
| import { CHAT_CLEAR, CHAT_FETCH, CHAT_FETCH_ERROR, CHAT_FETCH_SUCCESS, CHAT_HEADER_FETCH, CHAT_HEADER_FETCH_ERROR, CHAT_HEADER_FETCH_SUCCESS, CHAT_NEW_FETCH, CHAT_NEW_FETCH_ERROR, CHAT_NEW_FETCH_SUCCESS, CHAT_ONE_FETCH, CHAT_ONE_FETCH_ERROR, CHAT_ONE_FETCH_SUCCESS, CHAT_ONE_SET, CHAT_SEND_ERROR, CHAT_SEND_FETCH, CHAT_SEND_SUCCESS, CHAT_SET } from "./chatActionConstants"; | |||
| import { CHAT_ADD_MESSAGE, CHAT_CLEAR, CHAT_FETCH, CHAT_FETCH_ERROR, CHAT_FETCH_SUCCESS, CHAT_HEADER_FETCH, CHAT_HEADER_FETCH_ERROR, CHAT_HEADER_FETCH_SUCCESS, CHAT_NEW_FETCH, CHAT_NEW_FETCH_ERROR, CHAT_NEW_FETCH_SUCCESS, CHAT_ONE_FETCH, CHAT_ONE_FETCH_ERROR, CHAT_ONE_FETCH_SUCCESS, CHAT_ONE_SET, CHAT_SEND_ERROR, CHAT_SEND_FETCH, CHAT_SEND_SUCCESS, CHAT_SET } from "./chatActionConstants"; | |||
| export const fetchChats = (payload) => ({ | |||
| type: CHAT_FETCH, | |||
| @@ -60,4 +60,8 @@ export const sendMessageError = () => ({ | |||
| }) | |||
| export const startNewChatError = () => ({ | |||
| type: CHAT_NEW_FETCH_ERROR | |||
| }) | |||
| export const addNewMessage = (payload) => ({ | |||
| type: CHAT_ADD_MESSAGE, | |||
| payload | |||
| }) | |||
| @@ -16,6 +16,8 @@ import { logoutUser, refreshUserToken } from "../actions/login/loginActions"; | |||
| // const baseURL = "http://192.168.88.143:3001/"; // DULE | |||
| // const baseURL = "http://192.168.88.175:3005/"; | |||
| const baseURL = "https://trampa-api-test.dilig.net/"; | |||
| // const baseURL = "http://192.168.88.150:3001/"; // DJOLE | |||
| // const baseURL = "http://localhost:3001/"; | |||
| //Interceptor unique name | |||
| export const accessTokensMiddlewareInterceptorName = "ACCESS_TOKEN_INTERCEPTOR"; | |||
| @@ -42,7 +44,8 @@ export default ({ dispatch }) => | |||
| const axiosResponse = await axios.post(`${baseURL}auth/refresh`, { | |||
| token: refresh, | |||
| }); | |||
| const newToken = axiosResponse.data.token; | |||
| const newToken = axiosResponse.data; | |||
| response.headers.Authorization = `Bearer ${newToken}`; | |||
| dispatch(refreshUserToken(newToken)); | |||
| } | |||
| @@ -1,32 +1,71 @@ | |||
| import { CHAT_CLEAR, CHAT_ONE_SET, CHAT_SET } from "../../actions/chat/chatActionConstants" | |||
| import createReducer from "../../utils/createReducer" | |||
| import { | |||
| CHAT_ADD_MESSAGE, | |||
| CHAT_CLEAR, | |||
| CHAT_ONE_SET, | |||
| CHAT_SET, | |||
| } from "../../actions/chat/chatActionConstants"; | |||
| import createReducer from "../../utils/createReducer"; | |||
| const initialState = { | |||
| latestChats: [], | |||
| selectedChat: {} | |||
| } | |||
| latestChats: [], | |||
| selectedChat: {}, | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [CHAT_SET]: setChats, | |||
| [CHAT_ONE_SET]: setOneChat, | |||
| [CHAT_CLEAR]: clearChats | |||
| }, | |||
| initialState | |||
| ) | |||
| { | |||
| [CHAT_SET]: setChats, | |||
| [CHAT_ONE_SET]: setOneChat, | |||
| [CHAT_CLEAR]: clearChats, | |||
| [CHAT_ADD_MESSAGE]: addNewMessage, | |||
| }, | |||
| initialState | |||
| ); | |||
| function setChats(state, action) { | |||
| return { | |||
| ...state, | |||
| latestChats: [...action.payload] | |||
| } | |||
| return { | |||
| ...state, | |||
| latestChats: [...action.payload], | |||
| }; | |||
| } | |||
| function setOneChat(state, action) { | |||
| return { | |||
| ...state, | |||
| selectedChat: action.payload | |||
| } | |||
| return { | |||
| ...state, | |||
| selectedChat: action.payload, | |||
| }; | |||
| } | |||
| function clearChats() { | |||
| return initialState; | |||
| } | |||
| return initialState; | |||
| } | |||
| function addNewMessage(state, { payload }) { | |||
| console.log(state); | |||
| console.log(payload); | |||
| let allChats = [...state.latestChats]; | |||
| let chat = allChats.find((item) => item.chat._id === payload._id); | |||
| if (chat) { | |||
| chat = { | |||
| ...chat, | |||
| chat: { | |||
| ...chat.chat, | |||
| messages: [...chat.chat.messages, payload.message], | |||
| }, | |||
| }; | |||
| allChats = allChats.filter((item) => item.chat._id !== chat.chat._id); | |||
| allChats = [chat, ...allChats]; | |||
| } | |||
| let newSelectedChat = {}; | |||
| if (state.selectedChat.chat) { | |||
| newSelectedChat = { ...state.selectedChat }; | |||
| if (newSelectedChat.chat._id === chat.chat._id) | |||
| newSelectedChat = { ...newSelectedChat, chat: chat.chat }; | |||
| } else { | |||
| newSelectedChat = { ...chat }; | |||
| } | |||
| console.log("chat", chat) | |||
| console.log("allChats", allChats); | |||
| console.log("newSelectedChat", newSelectedChat); | |||
| return { | |||
| ...state, | |||
| latestChats: [...allChats], | |||
| selectedChat: { ...newSelectedChat }, | |||
| }; | |||
| } | |||
| @@ -14,11 +14,26 @@ import { | |||
| CHAT_ONE_FETCH, | |||
| CHAT_SEND_FETCH, | |||
| } from "../actions/chat/chatActionConstants"; | |||
| import { fetchChatsError, fetchChatsSuccess, fetchHeaderChatsError, fetchHeaderChatsSuccess, fetchOneChatError, fetchOneChatSuccess, sendMessageError, sendMessageSuccess, setChats, setOneChat, startNewChatError, startNewChatSuccess } from "../actions/chat/chatActions"; | |||
| import { | |||
| addNewMessage, | |||
| fetchChatsError, | |||
| fetchChatsSuccess, | |||
| fetchHeaderChatsError, | |||
| fetchHeaderChatsSuccess, | |||
| fetchOneChatError, | |||
| fetchOneChatSuccess, | |||
| sendMessageError, | |||
| sendMessageSuccess, | |||
| setChats, | |||
| setOneChat, | |||
| startNewChatError, | |||
| startNewChatSuccess, | |||
| } from "../actions/chat/chatActions"; | |||
| import { validateExchange } from "../actions/exchange/exchangeActions"; | |||
| import { selectSelectedChat } from "../selectors/chatSelectors"; | |||
| import { selectExchange } from "../selectors/exchangeSelector"; | |||
| import { selectUserId } from "../selectors/loginSelectors"; | |||
| import { sendMessage as SendMessageSocket } from "../../socket/socket"; | |||
| function* fetchChats() { | |||
| try { | |||
| @@ -91,12 +106,30 @@ function* startNewChat(payload) { | |||
| attemptCreateNewChat, | |||
| payload.payload.offerId | |||
| ); | |||
| const userId = yield select(selectUserId); | |||
| const data = yield call(attemptFetchChats, userId); | |||
| yield put(setChats([...data.data])); | |||
| console.log(newChatData); | |||
| const newChatId = newChatData.data.chatId; | |||
| const messageObject = { | |||
| text: payload.payload.message, | |||
| }; | |||
| yield call(attemptSendMessage, newChatId, messageObject); | |||
| yield put(startNewChatSuccess()); | |||
| console.log(payload); | |||
| yield call( | |||
| SendMessageSocket, | |||
| newChatData.data.chatId, | |||
| userId, | |||
| payload.payload.message, | |||
| payload.payload.interlucatorUserId | |||
| ); | |||
| yield put( | |||
| addNewMessage({ | |||
| _id: newChatId, | |||
| message: { | |||
| userId, | |||
| text: payload.payload.message, | |||
| _created: new Date().toISOString(), | |||
| }, | |||
| }) | |||
| ); | |||
| if (payload.payload.handleMessageSendSuccess) { | |||
| yield call(payload.payload.handleMessageSendSuccess, newChatId); | |||
| } | |||
| @@ -71,7 +71,7 @@ function* fetchLogin({ payload }) { | |||
| } | |||
| console.log(e.response.status); | |||
| let errorMessage = yield call(rejectErrorCodeHelper, e.response.status); | |||
| if (e.response.status === 400 || e.response.status === 404) { | |||
| if (e.response.status === 401 || e.response.status === 404) { | |||
| errorMessage = i18next.t("login.wrongCredentials", { | |||
| lng: "rs" | |||
| }); | |||
| @@ -127,8 +127,10 @@ function* fetchOffers(payload) { | |||
| "?" + newQueryString.toString() | |||
| ); | |||
| yield put(setTotalOffers(data.data.total)); | |||
| yield put(setOffers(data.data.offers.filter(offer => !offer.pinned))); | |||
| yield put(setPinnedOffers(data.data.offers.filter(offer => offer.pinned))); | |||
| yield put(setOffers(data.data.offers.filter((offer) => !offer.pinned))); | |||
| yield put( | |||
| setPinnedOffers(data.data.offers.filter((offer) => offer.pinned)) | |||
| ); | |||
| yield put(fetchOffersSuccess()); | |||
| } catch (e) { | |||
| yield put(fetchOffersError()); | |||
| @@ -189,7 +191,19 @@ function* fetchMoreOffers(payload) { | |||
| function* createOffer(payload) { | |||
| try { | |||
| yield call(attemptAddOffer, payload.payload.values.offerData); | |||
| const offerData = payload.payload.values.offerData; | |||
| const formData = new FormData(); | |||
| formData.append("category[name]", offerData.category.name); | |||
| formData.append("condition", offerData.condition); | |||
| formData.append("description", offerData.description); | |||
| // formData.append("file", JSON.stringify(offerData.images)) | |||
| for (var i = 0; i < offerData.images.length; i++) { | |||
| formData.append("file", offerData.images[i]); | |||
| } | |||
| formData.append("location[city]", offerData.location.city); | |||
| formData.append("name", offerData.name); | |||
| formData.append("subcategory", offerData.subcategory); | |||
| yield call(attemptAddOffer, formData); | |||
| yield put(addOfferSuccess()); | |||
| if (payload.payload.handleApiResponseSuccess) { | |||
| yield call(payload.payload.handleApiResponseSuccess); | |||
| @@ -260,8 +274,32 @@ function* removeOffer(payload) { | |||
| function* editOffer(payload) { | |||
| try { | |||
| const offerId = payload.payload.offerId; | |||
| const editedData = payload.payload.offerData; | |||
| yield call(attemptEditOffer, offerId, editedData); | |||
| // const editedData = payload.payload.offerData; | |||
| const offerData = payload.payload.offerData; | |||
| const formData = new FormData(); | |||
| formData.append("category[name]", offerData.category.name); | |||
| formData.append("condition", offerData.condition); | |||
| formData.append("description", offerData.description); | |||
| // const oldImages = []; | |||
| for (var i = 0; i < offerData.images.length; i++) { | |||
| if (offerData.images[i] !== null) { | |||
| if (typeof offerData.images[i] === "string") { | |||
| formData.append("images[]", offerData.images[i]); | |||
| } else { | |||
| formData.append("file", offerData.images[i]); | |||
| } | |||
| } | |||
| } | |||
| // if (oldImages.length > 0) { | |||
| // formData.append("images", JSON.stringify(oldImages)); | |||
| // } | |||
| // if (oldImages.length === offerData.images.length) { | |||
| // formData.append("file", ""); | |||
| // } | |||
| formData.append("location[city]", offerData.location.city); | |||
| formData.append("name", offerData.name); | |||
| formData.append("subcategory", offerData.subcategory); | |||
| yield call(attemptEditOffer, offerId, formData); | |||
| yield put(editOfferSuccess()); | |||
| if (payload.payload.handleApiResponseSuccess) { | |||
| yield call(payload.payload.handleApiResponseSuccess); | |||
| @@ -46,44 +46,59 @@ function* fetchMineProfile() { | |||
| function* changeMineProfile(payload) { | |||
| try { | |||
| let image; | |||
| if (payload.payload.firmLogo.includes("data:image")) { | |||
| image = payload.payload.firmLogo | |||
| .replace("data:image/jpeg;base64,", "") | |||
| .replace("data:image/jpg;base64,", "") | |||
| .replace("data:image/png;base64,", ""); | |||
| } else if (payload.payload.firmLogo === "") { | |||
| image = ""; | |||
| } | |||
| // console.log(payload); | |||
| // let image; | |||
| // if (payload.payload.firmLogo) { | |||
| // image = payload.payload.firmLogo; | |||
| // } else if (payload.payload.firmLogo === "") { | |||
| // image = ""; | |||
| // } | |||
| // const reqData = { | |||
| // company: { | |||
| // name: payload.payload.firmName, | |||
| // PIB: payload.payload.firmPIB, | |||
| // contacts: { | |||
| // telephone: payload.payload.firmPhone.toString(), | |||
| // location: payload.payload.firmLocation ?? "", | |||
| // web: payload.payload.firmWebsite, | |||
| // }, | |||
| // }, | |||
| // image: image, | |||
| // }; | |||
| const reqData = { | |||
| company: { | |||
| name: payload.payload.firmName, | |||
| PIB: payload.payload.firmPIB, | |||
| contacts: { | |||
| telephone: payload.payload.firmPhone.toString(), | |||
| location: payload.payload.firmLocation ?? "", | |||
| web: payload.payload.firmWebsite, | |||
| }, | |||
| }, | |||
| image: image, | |||
| }; | |||
| // if (payload.payload.firmLogo?.includes("https")) delete reqData.image; | |||
| // if (reqData.company.contacts.telephone.length === 0) | |||
| // delete reqData.company.contacts.telephone; | |||
| // if (reqData.company.contacts.location.length === 0) | |||
| // delete reqData.company.contacts.location; | |||
| // if (reqData.company.contacts.web.length === 0) | |||
| // delete reqData.company.contacts.web; | |||
| if (payload.payload.firmLogo.includes("https")) delete reqData.image; | |||
| if (reqData.company.contacts.telephone.length === 0) | |||
| delete reqData.company.contacts.telephone; | |||
| if (reqData.company.contacts.location.length === 0) | |||
| delete reqData.company.contacts.location; | |||
| if (reqData.company.contacts.web.length === 0) | |||
| delete reqData.company.contacts.web; | |||
| const requestBody = new FormData(); | |||
| if (typeof payload.payload.firmLogo !== "string") | |||
| requestBody.append("file", payload.payload.firmLogo); | |||
| requestBody.append("company[name]", payload.payload.firmName); | |||
| requestBody.append("company[PIB]", payload.payload.firmPIB); | |||
| if (payload.payload.firmPhone.toString().length !== 0) | |||
| requestBody.append( | |||
| "company[contacts][telephone]", | |||
| payload.payload.firmPhone | |||
| ); | |||
| if (payload.payload.firmLocation.toString().length !== 0) | |||
| requestBody.append( | |||
| "company[contacts][location]", | |||
| payload.payload.firmLocation | |||
| ); | |||
| if (payload.payload.firmWebsite.toString().length !== 0) | |||
| requestBody.append("company[contacts][web]", payload.payload.firmWebsite); | |||
| const userId = yield select(selectUserId); | |||
| const data = yield call(attemptEditProfile, userId, reqData); | |||
| yield call(attemptEditProfile, userId, requestBody); | |||
| yield put(editMineProfileSuccess()); | |||
| if (payload.payload.handleApiResponseSuccess) { | |||
| yield call(payload.payload.handleApiResponseSuccess); | |||
| } | |||
| console.log(data); | |||
| } catch (e) { | |||
| yield put(editMineProfileError()); | |||
| console.dir(e); | |||
| @@ -21,30 +21,19 @@ import i18next from "i18next"; | |||
| function* fetchRegisterUser({ payload }) { | |||
| try { | |||
| let requestData = { | |||
| email: payload.values.mail.toString(), | |||
| password: payload.values.password.toString(), | |||
| image: payload.values.image | |||
| .replace("data:image/jpeg;base64,", "") | |||
| .replace("data:image/jpg;base64,", "") | |||
| .replace("data:image/png;base64,", ""), | |||
| company: { | |||
| name: payload.values.nameOfFirm.toString(), | |||
| PIB: payload.values.PIB.toString(), | |||
| contacts: { | |||
| telephone: payload.values.phoneNumber.toString(), | |||
| location: payload.values.location.toString(), | |||
| web: payload.values.website.toString(), | |||
| }, | |||
| }, | |||
| }; | |||
| if (payload.values.phoneNumber?.length === 0) | |||
| delete requestData.company.contacts.telephone; | |||
| if (payload.values.location?.length === 0) | |||
| delete requestData.company.contacts.location; | |||
| if (payload.values.website?.length === 0) | |||
| delete requestData.company.contacts.web; | |||
| yield call(attemptRegister, requestData); | |||
| const requestBody = new FormData(); | |||
| requestBody.append("email", payload.values.mail); | |||
| requestBody.append("password", payload.values.password); | |||
| requestBody.append("file", payload.values.image); | |||
| requestBody.append("company[name]", payload.values.nameOfFirm); | |||
| requestBody.append("company[PIB]", payload.values.PIB); | |||
| if (payload.values.phoneNumber.toString().length !== 0) | |||
| requestBody.append("company[contacts][telephone]", payload.values.phoneNumber); | |||
| if (payload.values.location.toString().length !== 0) | |||
| requestBody.append("company[contacts][location]", payload.values.location); | |||
| if (payload.values.website.toString().length !== 0) | |||
| requestBody.append("company[contacts][web]", payload.values.website); | |||
| yield call(attemptRegister, requestBody); | |||
| const { data } = yield call(attemptLogin, { | |||
| email: payload.values.mail, | |||
| @@ -1,10 +1,8 @@ | |||
| import history from "../../store/utils/history"; | |||
| export const startChat = (chats, offer, userId) => { | |||
| console.log(offer); | |||
| const chatItem = chats.find( | |||
| (item) => | |||
| item.chat.offerId === offer?._id && offer?.userId !== userId | |||
| (item) => item.chat.offerId === offer?._id && offer?.userId !== userId | |||
| ); | |||
| if (chatItem !== undefined) { | |||
| history.push(`/messages/${chatItem.chat._id}`); | |||
| @@ -0,0 +1,44 @@ | |||
| const CLOUDFLARE = "CLOUDFLARE"; | |||
| // const IMAGE_KIT = "IMAGEKIT"; | |||
| const IMAGE_PLATFORM = CLOUDFLARE; | |||
| export const variants = { | |||
| offerCard: "offerCard", | |||
| offerDetails: "offerDetails", | |||
| profileImage: "profileImage", | |||
| reviewCard: "reviewCard", | |||
| chatHeader: "chatHeader", | |||
| chatMessage: "chatMessage", | |||
| chatCard: "chatCard", | |||
| deleteChat: "chatHeader", | |||
| profileCard: "profileCard", | |||
| createReviewCard: "createReviewCard" | |||
| } | |||
| const cloudFlareVariants = { | |||
| offerCard: "primary", | |||
| offerCardMobile: "primaryMobile", | |||
| offerDetails: "primary", | |||
| offerDetailsMobile: "primary", | |||
| profileImage: "primary", | |||
| profileImageMobile: "profileMobile", | |||
| reviewCard: "review", | |||
| reviewCardMobile: "review", | |||
| chatHeader: "chatHeader", | |||
| chatHeaderMobile: "chatHeader", | |||
| chatMessage: "chatHeader", | |||
| chatMessageMobile: "chatHeader", | |||
| chatCard: "chatCard", | |||
| chatCardMobile: "chatCard", | |||
| profileCard: "profileCard", | |||
| createReviewCard: "primaryMobile", | |||
| } | |||
| export const getImageUrl = (imageUrl, variant, isMobile = false) => { | |||
| let imageVariant = ""; | |||
| if (IMAGE_PLATFORM === CLOUDFLARE) { | |||
| imageVariant = isMobile ? cloudFlareVariants[variant + "Mobile"] : cloudFlareVariants[variant]; | |||
| } | |||
| return imageUrl + imageVariant; | |||
| } | |||
| @@ -6,13 +6,10 @@ export default Yup.object().shape({ | |||
| .required(i18n.t("editProfile.labelPIBRequired")) | |||
| .min(9, i18n.t("register.PIBnoOfCharacters")) | |||
| .max(9, i18n.t("register.PIBnoOfCharacters")), | |||
| firmLocation: Yup.string().required( | |||
| i18n.t("editProfile.labelLocationRequired") | |||
| ), | |||
| firmLocation: Yup.string(), | |||
| firmWebsite: Yup.string(), | |||
| firmApplink: Yup.string(), | |||
| firmPhone: Yup.string() | |||
| .required(i18n.t("editProfile.labelPhoneRequired")) | |||
| .min(6, i18n.t("editProfile.labelPhoneValid")) | |||
| .max(14, i18n.t("editProfile.labelPhoneValid")), | |||
| }); | |||