Untitled
unknown
plain_text
a year ago
63 kB
4
Indexable
/* eslint-disable react-hooks/exhaustive-deps */ import React, { useEffect, useState } from "react"; import axios from "axios"; import { z } from "zod"; import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, Badge, ModalBody, ModalCloseButton, Input, Button, VStack, FormControl, FormLabel, Select, SimpleGrid, GridItem, Box, List, ListItem, useToast, Tag, Divider, Textarea, Text, TagLabel, TagCloseButton, Avatar, Flex, HStack, Tooltip, FormErrorMessage, useTabPanel, } from "@chakra-ui/react"; import { Keyword, RecordInterface, } from "@/interface/due-diligence/RecordInterface"; import { Niche } from "@/interface/due-diligence/NicheInterface"; import { useCreateNicheMutation, useSearchNicheQuery, useGetOneNicheQuery, useGetAllNichesQuery, } from "@/redux/api/niche.api"; import { useCreateKeywordMutation, useSearchKeywordQuery, useGetOneKeywordQuery, } from "@/redux/api/keyword.api"; import { useGetOneRecordQuery } from "@/redux/api/due-diligence.api"; import { Comment } from "@/interface/due-diligence/RecordInterface"; import { useDeleteCommentMutation } from "@/redux/api/due-diligence.api"; import Image from "next/image"; import { AiFillCaretDown } from "react-icons/ai"; import { useAppSelector } from "@/redux/hooks"; import validationScripts from "@/util/validation"; import { Formik, Field, useFormik } from "formik"; import * as yup from "yup"; // constants import countries from "@/constants/datasets/countries.json"; import states from "@/constants/datasets/states.json"; import TooltipCustom from "../tooltipCustom/tooltipCustom"; const formValidationSchema = z.object({ availableDomain: z .string() .regex(/^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/) .min(1), competitiveBusiness1: z .string() .regex( /^(?!https?:\/\/|www\.)([A-Za-z0-9-]+\.){1}[A-Za-z]{2,6}(?:\/[^\/]*)?$/ ) .min(1), competitiveBusiness2: z .string() .regex( /^(?!https?:\/\/|www\.)([A-Za-z0-9-]+\.){1}[A-Za-z]{2,6}(?:\/[^\/]*)?$/ ) .min(1), competitiveBusiness3: z .string() .regex( /^(?!https?:\/\/|www\.)([A-Za-z0-9-]+\.){1}[A-Za-z]{2,6}(?:\/[^\/]*)?$/ ) .min(1), }); interface CustomModalProps { isOpen: boolean; onClose: () => void; _id?: string; record: RecordInterface | undefined; onSave: (editedRecord: RecordInterface) => void; } const AddRecordModal: React.FC<CustomModalProps> = ({ isOpen, onClose, record, _id, onSave, }) => { const toast = useToast(); const [country, setCountry] = useState<string>("United States"); const [domainAvailable, setDomainAvailable] = useState(""); const [searchNiche, setSearchNiche] = useState(""); const [nicheRef, setNicheRef] = useState(record?.nicheRef || ""); const [selectedNiche, setSelectedNiche] = useState(""); const [population, setPopulation] = useState<number>(0); const [searchKeyword, setSearchKeyword] = useState(""); const [selectedKeyword, setSelectedKeyword] = useState(""); const [keywordRef, setKeywordRef] = useState(record?.keywordRef || ""); const [searchSecondaryKeyword, setSearchSecondaryKeyword] = useState(""); const [secondaryKeyword, setSecondaryKeyword] = useState({ value: "", _id: "", }); const [secondaryKeywordsArr, setSecondaryKeywordsArr] = useState<Niche[]>([]); const [location, setLocation] = useState(record?.cityName || "us"); const [cityName, setCityName] = useState(record?.cityName || ""); const [competitiveBusinesses, setCompetitiveBusinesses] = useState([ "", "", "", ]); const [isGo, setIsGo] = useState<string>(record?.isGo || "TBD"); const [activeBusiness, setActiveBusiness] = useState("NONE"); const [isLowHangingFruit, setIsLowHangingFruit] = useState<string>( record?.isLowHangingFruit || "UNSURE" ); const [callDrivenNiche, setCallDrivenNiche] = useState<string>( record?.callDrivenNiche || "NO" ); const [comment, setComment] = useState<string>(""); const [comments, setComments] = useState<Comment[]>([]); const [createNiche] = useCreateNicheMutation(); const [createKeyword] = useCreateKeywordMutation(); const { data: searchPrimaryKeywordData } = useSearchKeywordQuery(searchKeyword); const { data: getAllNiches } = useGetAllNichesQuery(); const { data: nicheRes } = useGetOneNicheQuery(searchNiche); const { data: keywordRes } = useGetOneKeywordQuery(searchKeyword); const { data: keywordRess } = useGetOneKeywordQuery(searchSecondaryKeyword); const { data: recordData }: any = useGetOneRecordQuery(_id || ""); const { data } = useSearchNicheQuery(searchNiche); const { data: keywordsData } = useSearchKeywordQuery(searchSecondaryKeyword); const [deleteComment, { isSuccess: CommentDeletionResponseStatus }] = useDeleteCommentMutation(); const [isStateFieldInFocus, setStateFieldFocusState] = useState<boolean>(false); const [isCityFieldInFocus, setCityFieldFocusState] = useState<boolean>(false); const [isNicheFieldInFocus, setNicheFieldFocusState] = useState<boolean>(false); const [isPrimaryKeyFieldInFocus, setPrimaryKeyFieldFocusState] = useState<boolean>(false); const [isSecondaryFieldInFocus, setSecondaryFieldFocusState] = useState<boolean>(false); const [countryData, setcountryData] = useState<string>("US"); const [statesData, setStatesData] = useState<any>(); const [selState, setSelState] = useState<any>({}); const [citiesData, setCitiesData] = useState<any>(); const [selCity, setSelCity] = useState<any>(""); const [validationErrors, setValidationErrors] = useState({ domainAvailable: "", selectedKeyword: "", location: "", competitiveBusinesses: ["", "", ""], }); const { isOpen: isMenuBarOpen } = useAppSelector( (state) => state.SideBarState ); useEffect(() => { if (isOpen) { resetForm(); } if (_id && recordData) { setDomainAvailable(recordData?.data[0]?.domainAvailable || ""); setSearchNiche(recordData.data[0]?.nicheRef.primaryNiche.value || ""); setSelectedNiche(recordData.data[0]?.nicheRef.primaryNiche.value || ""); setNicheRef(recordData.data[0]?.nicheRef.primaryNiche._id || ""); setSearchKeyword( recordData.data[0]?.keywordRefs?.primaryKeyword?.value || "" ); setSelectedKeyword( recordData.data[0]?.keywordRefs?.primaryKeyword?.value || "" ); setKeywordRef(recordData.data[0]?.keywordRefs?.primaryKeyword?._id || ""); const competitiveBusinessLinks = Array.from( { length: 3 }, (_, index) => recordData?.data[0]?.competitiveBusinesses?.[index]?.businessLink || "" ); const [business1, business2, business3] = competitiveBusinessLinks; setCompetitiveBusinesses([business1, business2, business3]); setIsGo(String(recordData?.data[0]?.isGo)); setActiveBusiness(recordData?.data[0]?.activeBusiness || "MANY"); setIsLowHangingFruit(recordData?.data[0]?.isLowHangingFruit); setCallDrivenNiche(recordData?.data[0]?.callDrivenNiche); setComments(recordData?.data[0]?.comments || []); const secondaryKeywordsArr = recordData?.data[0]?.keywordRefs.secondaryKeywords || []; const extractedSecondaryKeywords = secondaryKeywordsArr.map( (keyword: any) => ({ value: keyword?.value || "", _id: keyword?._id || "", }) ); setSecondaryKeywordsArr(extractedSecondaryKeywords); } }, [recordData, _id, isOpen]); const handleNicheClick = (nicheValue: string, nicheRef: string) => { setSearchNiche(nicheValue); setSelectedNiche(nicheValue); setNicheRef(nicheRef); }; const handlePrimaryKeywordClick = (value: string, _id: string) => { if (!location) { setLocation("us"); } setSearchKeyword(value); setSelectedKeyword(value); setKeywordRef(_id); }; const isSecondaryKeywordAlreadyPresent = (id: string): boolean => { return ( secondaryKeywordsArr.filter((val: any) => val._id === id).length === 0 ); }; useEffect(() => { if ( secondaryKeyword.value && secondaryKeyword.value.trim() !== "" && isSecondaryKeywordAlreadyPresent(secondaryKeyword._id) ) { if (secondaryKeywordsArr.length > 5) { toast({ description: "Secondary keyword limit exceeded!", status: "error", isClosable: true, duration: 4500, }); return; } setSecondaryKeywordsArr([...secondaryKeywordsArr, secondaryKeyword]); setSecondaryKeyword({ value: "", _id: "" }); } }, [secondaryKeyword, secondaryKeywordsArr]); const handleMultipleKeywordsClick: ( value: string, _id: string ) => void = async (value, _id) => { if (secondaryKeywordsArr.length > 6) { toast({ description: "Secondary keyword limit exceeded!", isClosable: true, duration: 4500, status: "error", }); return; } setSecondaryKeyword({ value, _id }); }; const sortStateData = () => { setStatesData( states.filter((item: any) => item.country_code === countryData) ); }; const fetchPopulationData = async (cityId: string) => { try { const { data: { data }, } = await axios.get( `https://wft-geo-db.p.rapidapi.com/v1/geo/cities/${cityId}`, { headers: { "X-RapidAPI-Key": process.env.NEXT_PUBLIC_RAPID_API_KEY, "X-RapidAPI-Host": process.env.NEXT_PUBLIC_RAPID_API_HOST, }, } ); setPopulation(data.population); } catch (err: any) { toast({ description: "Population Api failed!", status: "error", }); } }; useEffect(() => { sortStateData(); }, [countryData]); useEffect(() => { if (selState) { sortCitiesData(); } }, [selState]); useEffect(() => { if (selCity) { const data = citiesData.filter((item: any) => item.id == selCity.id); fetchPopulationData(data[0].wikiDataId); } }, [selCity]); const toastNicheCreated = (value: string) => { toast({ title: `${value} created`, status: "success", duration: 3000, isClosable: true, }); }; const toastNicheError = () => { toast({ title: "Error", description: "An error occurred while creating.", status: "error", duration: 3000, isClosable: true, }); }; const handleCommentDeletion = async (commentId: string) => { try { if (_id && commentId) { await deleteComment({ id: _id, commentId }); } if (CommentDeletionResponseStatus) { toast({ title: "Comment Deleted Successfully!", status: "success", duration: 9000, isClosable: true, }); } } catch (err: any) { toast({ description: "Failed to delete comment!", status: "error", duration: 9000, isClosable: true, }); } }; const validateForm = () => { let isValid = true; const errors = { domainAvailable: "", selectedKeyword: "", cityName: "", competitiveBusinesses: ["", "", ""], }; // Validate domainAvailable if (!domainAvailable) { isValid = false; errors.domainAvailable = "Domain is required"; } // Validate keyword if (!selectedKeyword) { isValid = false; errors.selectedKeyword = "Keyword is required"; } // Validate location if (!cityName) { isValid = false; errors.cityName = "Location is required"; } setValidationErrors(errors as any); return isValid; }; const resetForm = () => { setDomainAvailable(""); setNicheRef(""); setSelectedNiche(""); setSearchKeyword(""); setKeywordRef(""); setLocation("us"); setCompetitiveBusinesses(["", "", ""]); setIsGo("TBD"); setActiveBusiness("NONE"); setIsLowHangingFruit("UNSURE"); setCallDrivenNiche("NO"); setSearchNiche(""); setComment(""); setComments([]); setSelectedKeyword(""); setSecondaryKeyword({ value: "", _id: "" }); setSecondaryKeywordsArr([]); setStateSearchText(""); setCityData([]); }; const [cityData, setCityData] = useState<any[]>([]); const [stateSearchText, setStateSearchText] = useState<string>(""); const [citySearchText, setCitySearchText] = useState<string>(""); const fetchCities = async () => { try { const { data: { results }, } = await axios.get( `https://public.opendatasoft.com//api/explore/v2.1/catalog/datasets/geonames-all-cities-with-a-population-1000/records?limit=100&refine=cou_name_en%3A%22${country .split(" ") .join("%20")}%22` ); setCityData(results); setCityName(results[0].name); setPopulation(results[0].population); } catch (err: any) { toast({ description: "Data for cities with population over 1000 failed to be collected!", duration: 4500, isClosable: true, status: "error", }); } }; useEffect(() => { if (isOpen) { fetchCities(); } }, [country, isOpen]); type DomainValidationErrors = { availableDomain: string; competitiveBusiness1: string; competitiveBusiness2: string; competitiveBusiness3: string; }; const [domainValidationErrors, setDomainValidationErrors] = useState<DomainValidationErrors>({ availableDomain: "", competitiveBusiness1: "", competitiveBusiness2: "", competitiveBusiness3: "", }); type domainValidationErrorKeys = keyof typeof domainValidationErrors; const handleBusinessChange = (index: number, value: string) => { //add validation // Additional validation to check for URL prefixes const urlPrefixes = ["http://", "https://", "www."]; const startsWithPrefix = urlPrefixes.some((prefix) => value.toLowerCase().startsWith(prefix) ); if (startsWithPrefix) { // Display error message for URL prefixes setDomainValidationErrors((prevErrors: DomainValidationErrors) => ({ ...prevErrors, [`competitiveBusiness${index + 1}`]: "Invalid URL prefix. Please enter only the root domain.", })); } else { // Update the state and clear any previous error message setDomainValidationErrors((prevErrors: DomainValidationErrors) => ({ ...prevErrors, [`competitiveBusiness${index + 1}`]: "", })); const updatedBusinesses = [...competitiveBusinesses]; updatedBusinesses[index] = value; setCompetitiveBusinesses(updatedBusinesses); } }; useEffect(() => { try { const d = formValidationSchema.parse({ availableDomain: domainAvailable, competitiveBusiness1: competitiveBusinesses[0], competitiveBusiness2: competitiveBusinesses[1], competitiveBusiness3: competitiveBusinesses[2], }); } catch (err: any) { if (err instanceof z.ZodError) { const issues = err.issues; issues.map((trace: any) => { switch (trace.path[0]) { case "availableDomain": if ( trace.validation === "regex" && domainAvailable.length !== 0 ) { setDomainValidationErrors({ ...domainValidationErrors, availableDomain: "Invalid Domain", }); } break; case "competitiveBusiness1": if ( trace.validation === "regex" && competitiveBusinesses[0].length !== 0 ) { setDomainValidationErrors({ ...domainValidationErrors, competitiveBusiness1: "Invalid Domain", }); } break; case "competitiveBusiness2": if ( trace.validation === "regex" && competitiveBusinesses[1].length !== 0 ) { setDomainValidationErrors({ ...domainValidationErrors, competitiveBusiness2: "Invalid Domain", }); } break; case "competitiveBusiness3": if ( trace.validation === "regex" && competitiveBusinesses[2].length !== 0 ) { setDomainValidationErrors({ ...domainValidationErrors, competitiveBusiness3: "Invalid Domain", }); } break; default: break; } }); } } }, [domainAvailable, competitiveBusinesses]); const formik = useFormik({ initialValues: { Comments: "", AvailableDomain: "", GoOrNoGo: "", LowHangingFruit: "", NumberOfActiveBusinesses: "", CompetitorBusiness1: "", CompetitorBusiness2: "", CompetitorBusiness3: "", SecondaryBusiness: "", PrimaryKeyword: "", SecondaryKeyword: "", CallDrivenNiche: "", Niche: "", City: "", // from use state State: "", }, onSubmit: async (values, actions) => { try { const editedRecord = { population, cityName: selCity.name, location: countryData, domainAvailable: values.AvailableDomain, nicheRef: values.Niche, keywordRef: values.PrimaryKeyword, secondaryKeywordsArr: values.SecondaryKeyword, competitiveBusinesses: [ values.CompetitorBusiness1, values.CompetitorBusiness2, values.CompetitorBusiness3, ], isGo: values.GoOrNoGo, activeBusiness: values.NumberOfActiveBusinesses, isLowHangingFruit: values.LowHangingFruit, callDrivenNiche: values.CallDrivenNiche, comment: values.Comments, }; onSave({ ...editedRecord, secondaryKeywordsArr: secondaryKeywordsArr, keywordRef: selectedKeyword, } as any); } catch (error: any) { toast({ description: error.data.message, duration: 4500, isClosable: true, status: "error", }); } }, validationSchema: yup.object({ CompetitorBusiness1: yup .string() .test( "is-valid-domain", "The entered URL is invalid! Please enter only the root domain and remove URL prefixes such as https, www, etc", (value) => !/^(https?:\/\/|www\.)/.test(value || "") ) .required("This field is required"), CompetitorBusiness2: yup .string() .test( "is-valid-domain", "The entered URL is invalid! Please enter only the root domain and remove URL prefixes such as https, www, etc", (value) => !/^(https?:\/\/|www\.)/.test(value || "") ) .required("This field is required"), CompetitorBusiness3: yup .string() .test( "is-valid-domain", "The entered URL is invalid! Please enter only the root domain and remove URL prefixes such as https, www, etc", (value) => !/^(https?:\/\/|www\.)/.test(value || "") ) .required("This field is required"), Niche: yup.string().required("Niche is required"), PrimaryKeyword: yup.string().required("Primary keyword is required"), GoOrNoGo: yup.string().required("Please select Go or No"), LowHangingFruit: yup.string().required("Please select low hanging fruit"), AvailableDomain: yup .string() .required("Please fill the Available domain"), }), }); const sortCitiesData = () => { fetch("https://stgapi.rankandrentengine.com/api/utils/cities", { method: "POST", body: JSON.stringify({ country_code: selState.country_code, state_code: selState.state_code, }), headers: { "Content-Type": "application/json" }, }) .then((data) => data.json()) .then((item) => { if (item && item.cities) { setCitiesData(item.cities); } }); }; const handleCreatePrimaryKeyword = async ($event: any) => { if ($event.key === "Enter") { if (!location) { toast({ description: "Please enter country inorder to add keywords!", status: "error", }); return; } setSelectedKeyword(formik.values.PrimaryKeyword); formik.setValues({ ...formik.values, PrimaryKeyword: "" }); } }; const handleRemovePrimaryKeyword = () => { formik.setValues({ ...formik.values, PrimaryKeyword: "" }); setSelectedKeyword(""); }; const handleRemoveNicheForNicheArr = (index: number) => { const updatedValues = secondaryKeywordsArr.filter((_, i) => i !== index); setSecondaryKeywordsArr(updatedValues); }; const handleKeyDownSecondaryKeyword = async ( event: React.KeyboardEvent<HTMLInputElement> ) => { if (event.key !== "Enter") { return; } if (secondaryKeywordsArr.length > 5) { toast({ description: "Secondary keyword limit exceeded!", status: "error", isClosable: true, duration: 4500, }); return; } event.preventDefault(); try { if (!location) { toast({ description: "Please enter country before proceeding", status: "error", }); return; } setSecondaryKeywordsArr((prev) => [ ...prev, { value: formik.values.SecondaryKeyword || "", _id: formik.values.SecondaryKeyword || "", }, ]); formik.setValues({ ...formik.values, SecondaryKeyword: "" }); } catch (error) { toastNicheError(); } }; const handleCreateNiche = async (niche: any) => { formik.setValues({ ...formik.values, Niche: niche.value }); }; const handleRemovePrimaryNiche = () => { formik.setValues({ ...formik.values, Niche: "" }); setSelectedNiche(""); }; const handleStateChange = (event: any) => { setStateSearchText(event.target.value); sortCitiesData(); }; const handleCityChange = (event: any) => { setCitySearchText(event.target.value); fetchPopulationData(selCity.wikiDataId); }; return ( <Modal size={"5xl"} isOpen={isOpen} onClose={onClose}> <ModalOverlay bg={"rgba(255, 255, 255, 0.6)"} backdropFilter="blur(4.5px)" /> <ModalContent bg={"#E1EDEE"} padding={"20px"} pt={"40px"} h={"85vh"} w={"75vw"} overflowX={"hidden"} overflowY={"auto"} > <ModalHeader color={"#022B3A"} fontSize={"2.1rem"} display={"flex"} alignItems={"center"} gap={"30px"} > <Text>Add Location</Text> <Tag p={"9px 16px"} color={"#fff"} size="lg" bg={"#022B3A"} borderRadius="18px" > <Avatar src="/assets/icons/tag_icon.svg" size="xs" name="Segun Adebayo" ml={-1} mr={2} /> <TagLabel fontSize={"1.5rem"} fontWeight={"700"}> #4854333 </TagLabel> </Tag> </ModalHeader> <ModalCloseButton /> <ModalBody w={"100%"} pt={"20px"} color={"#022B3A"}> <VStack gap={"0"}> <HStack bg={"#E1EDEE"} pt={"10px"} width={"100%"} justifyContent={"space-between"} borderRadius={"15px 15px 0 0"} border={"3px solid rgba(174, 214, 220, 0.36)"} alignItems={"flex-end"} > <Text pl={"15px"} pr={"40px"} borderBottom={"4px solid"} fontSize={"1.5rem"} fontWeight={"700"} > {_id ? "Update" : "Create "} Record </Text> </HStack> <Box border={"2px"} w={"100%"} pr={"40px"} bgColor={"rgba(255, 255, 255, 0.45)"} > <form onSubmit={formik.handleSubmit}> <HStack pb={"30px"} w={"100%"} alignItems={"start"} gap={"5vw"}> <Box pl={"15px"} w="20vw"> <Text fontSize={"22px"} fontWeight={"600"}> {" "} General Info </Text> <Text color={"#7A7A7A"}> Add details about your market, niche, and core keywords. </Text> </Box> <VStack w={"100%"} mt={"10px"} pb={"20px"} borderBottom={"1px solid rgba(2, 43, 58, 0.3)"} > <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>Country</Text> <Tooltip bg={"#fff"} color={"#5F7F9E"} placement="top" hasArrow label="Select the country for your prospective market." > <Image src={"/assets/icons/qus_icon.svg"} alt="qus icon" height={21} width={21} /> </Tooltip> </FormLabel> <Select textTransform={"capitalize"} icon={<AiFillCaretDown />} bg={"#fff"} onChange={(e) => { setcountryData(e.target.value); }} > {countries?.map((item, index) => { return ( <option key={index} value={item.iso2} selected={item.name === "United States"} disabled={item.name !== "United States"} > {item.name} </option> ); })} </Select> <FormErrorMessage> {validationErrors.location} </FormErrorMessage> </FormControl> <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>State</Text> <Tooltip bg={"#fff"} color={"#5F7F9E"} placement="top" hasArrow label="Enter a state for your prospective market." > <Image src={"/assets/icons/qus_icon.svg"} alt="qus icon" height={21} width={21} /> </Tooltip> </FormLabel> <VStack> <Input bg={"#fff"} name="State" placeholder="Search for State" value={ stateSearchText === "" ? selState.name : stateSearchText } onChange={(e) => handleStateChange(e)} _placeholder={{ color: "#7A7A7A", }} onFocus={() => setStateFieldFocusState(true)} onBlur={() => setTimeout( () => setStateFieldFocusState(false), 500 ) } /> {isStateFieldInFocus && ( <Box w={"100%"} position={"absolute"} bg={"#fff"} zIndex={100} top={"74px"} h={"15vh"} overflow="scroll" > {statesData && statesData ?.filter( (state: any) => state.name.toLowerCase() !== stateSearchText.toLowerCase() ) .map((state: any, index: number) => ( <Text p={2} cursor="pointer" _hover={{ backgroundColor: "teal.500", color: "#fff", }} key={index} onClick={() => { setSelState(state); setStateSearchText(""); }} > {state.name} </Text> ))} </Box> )} </VStack> <FormErrorMessage> {validationErrors.location} </FormErrorMessage> </FormControl> <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>City</Text> <Tooltip bg={"#fff"} color={"#5F7F9E"} placement="top" hasArrow label="Enter a city for your prospective market." > <Image src={"/assets/icons/qus_icon.svg"} alt="qus icon" height={21} width={21} /> </Tooltip> </FormLabel> <VStack> <Input bg={"#fff"} placeholder="Search for City" name="City" value={selCity.name} onChange={(e) => handleCityChange(e)} _placeholder={{ color: "#7A7A7A", }} onFocus={() => setCityFieldFocusState(true)} onBlur={() => setTimeout(() => setCityFieldFocusState(false), 500) } /> {isCityFieldInFocus && ( <Box w={"100%"} position={"absolute"} bg={"#fff"} zIndex={100} top={"74px"} h={"15vh"} overflow="scroll" > {citiesData && citiesData .filter( (city: any) => city.name.toLowerCase() !== stateSearchText.toLowerCase() ) .map((city: any, index: number) => ( <Text p={2} _hover={{ backgroundColor: "teal.500", color: "#fff", }} key={index} onClick={() => setSelCity(city)} > {city.name} </Text> ))} </Box> )} </VStack> <FormErrorMessage> {validationErrors.location} </FormErrorMessage> </FormControl> <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>Niche</Text> <Tooltip bg={"#fff"} color={"#5F7F9E"} placement="top" hasArrow label="Enter a niche for your prospective market." > <Image src={"/assets/icons/qus_icon.svg"} alt="qus icon" height={21} width={21} /> </Tooltip> </FormLabel> <Box> <Input bgColor="#fff" name="Niche" value={formik.values.Niche} onChange={formik.handleChange} placeholder="Search or create a niche" _placeholder={{ color: "#7A7A7A", }} onFocus={() => setNicheFieldFocusState(true)} onBlur={() => setTimeout( () => setNicheFieldFocusState(false), 500 ) } /> {isNicheFieldInFocus && getAllNiches && getAllNiches.data .filter( (niche: any) => niche.value.toLowerCase() !== formik.values.Niche.toLowerCase() ) .map((niche: any, index: number) => ( <Text p={2} _hover={{ backgroundColor: "teal.500", color: "#fff", }} key={index} onClick={() => handleCreateNiche(niche)} > {niche.value} </Text> ))} {formik.errors.Niche && formik.touched.Niche && ( <Text fontWeight={500} fontSize="sm" color="tomato"> {formik.errors.Niche} </Text> )} </Box> </FormControl> <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>Call Driven Niche?</Text> <Tooltip bg={"#fff"} color={"#5F7F9E"} placement="top" hasArrow label="Some niches rely almost exclusively on phone calls rather than a walk-in client. Call-driven niches usually work best for rank and rent, but there are exceptions. Whether the niche is call driven is an important factor in due diligence." > <Image src={"/assets/icons/qus_icon.svg"} alt="qus icon" height={21} width={21} /> </Tooltip> </FormLabel> <Select textTransform={"capitalize"} icon={<AiFillCaretDown />} bg={"#fff"} name="CallDrivenNiche" value={formik.values.CallDrivenNiche} onChange={formik.handleChange} > <option value={""}>Select</option> <option value={"YES"}>Yes </option> <option value={"NO"}>No</option> </Select> </FormControl> <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>Primary Keyword</Text> <TooltipCustom tooltipText="Enter the primary keyword you want to rank for in your prospective market and press enter. This is usually the same keyword as for your landing page." /> </FormLabel> <Box> <Input bgColor="#fff" value={formik.values.PrimaryKeyword} name="PrimaryKeyword" onChange={formik.handleChange} placeholder="Search or create a keyword and press enter" onFocus={() => setPrimaryKeyFieldFocusState(true)} onBlur={() => setTimeout( () => setPrimaryKeyFieldFocusState(false), 500 ) } onKeyDown={handleCreatePrimaryKeyword} _placeholder={{ color: "#7A7A7A", }} /> {selectedKeyword && ( <Tag mt={2} variant="solid" colorScheme="teal"> {selectedKeyword} <Button size="sm" variant="unstyled" onClick={handleRemovePrimaryKeyword} > X </Button> </Tag> )} {formik.errors.PrimaryKeyword && ( <Text fontWeight={500} fontSize="sm" color="tomato"> {formik.errors.PrimaryKeyword} </Text> )} </Box> </FormControl> <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>Secondary Keywords</Text> <TooltipCustom tooltipText="Enter up to 6 additional secondary keywords for your prospective market.These are usually the same keywords used for your service pages" /> </FormLabel> <Input bg={"#fff"} type="text" name="SecondaryKeyword" placeholder="Enter secondary keywords and press enter to add" value={formik.values.SecondaryKeyword} onChange={formik.handleChange} onKeyDown={handleKeyDownSecondaryKeyword} onFocus={() => setSecondaryFieldFocusState(true)} onBlur={() => setTimeout( () => setSecondaryFieldFocusState(false), 500 ) } _placeholder={{ color: "#7A7A7A", }} /> <Box mt="2"> {secondaryKeywordsArr.map((secondaryKeyword, index) => ( <Tag key={index} size="md" variant="subtle" colorScheme="blue" mr="2" mb="2" > <TagLabel>{secondaryKeyword.value}</TagLabel> <TagCloseButton onClick={() => handleRemoveNicheForNicheArr(index) } /> </Tag> ))} </Box> {formik.errors.SecondaryKeyword && formik.touched.SecondaryKeyword && ( <Text fontWeight={500} fontSize="sm" color="tomato"> {formik.errors.SecondaryKeyword} </Text> )} </FormControl> </VStack> </HStack> <HStack w={"100%"} alignItems={"start"} gap={"5vw"}> <Box w={"20vw"} pl={"15px"}> <Text fontSize={"22px"} fontWeight={"600"}> {" "} Competitor Analysis </Text> <Text color={"#7A7A7A"}> Add the domains for your top 3 competitors in your prospective market to assess your ranking difficulty. </Text> </Box> <VStack w={"100%"} pb={"30px"} borderBottom={"1px solid rgba(2, 43, 58, 0.3)"} > <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>How Many Active Businesses?</Text> <Tooltip bg={"#fff"} color={"#5F7F9E"} placement="top" hasArrow label="A market should have enough active businesses to prospect for a client. Assess how many businesses exist in this market and assign a choice here," > <Image src={"/assets/icons/qus_icon.svg"} alt="qus icon" height={21} width={21} /> </Tooltip> </FormLabel> <Select textTransform={"capitalize"} icon={<AiFillCaretDown />} bg={"#fff"} name="NumberOfActiveBusinesses" value={formik.values.NumberOfActiveBusinesses} onChange={formik.handleChange} > <option value="MANY">Many</option> <option value="FEW">Few</option> <option value="NONE">None</option> </Select> </FormControl> <FormControl w={"100%"}> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>{`Competitor Domain `}</Text> <TooltipCustom tooltipText={`Enter the domain for the of the three top competitors in your market for competitive analysis.`} /> </FormLabel> <Input bg={"#fff"} placeholder={`Business 1`} name="CompetitorBusiness1" value={formik.values.CompetitorBusiness1} pattern="[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@%_\+.~#?&//=]*)" onChange={formik.handleChange} _placeholder={{ color: "#7A7A7A", }} /> {formik.errors.CompetitorBusiness1 && formik.touched.CompetitorBusiness1 && ( <Text fontWeight={500} fontSize="sm" color="tomato"> {formik.errors.CompetitorBusiness1} </Text> )} </FormControl> <FormControl w={"100%"}> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>{`Competitor Domain `}</Text> <TooltipCustom tooltipText={`Enter the domain for the of the three top competitors in your market for competitive analysis.`} /> </FormLabel> <Input bg={"#fff"} placeholder={`Business 2`} name="CompetitorBusiness2" value={formik.values.CompetitorBusiness2} pattern="[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@%_\+.~#?&//=]*)" onChange={formik.handleChange} _placeholder={{ color: "#7A7A7A", }} /> {formik.errors.CompetitorBusiness2 && formik.touched.CompetitorBusiness2 && ( <Text fontWeight={500} fontSize="sm" color="tomato"> {formik.errors.CompetitorBusiness2} </Text> )} </FormControl> <FormControl w={"100%"}> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>{`Competitor Domain `}</Text> <TooltipCustom tooltipText={`Enter the domain for the of the three top competitors in your market for competitive analysis.`} /> </FormLabel> <Input bg={"#fff"} placeholder={`Business 3`} name="CompetitorBusiness3" value={formik.values.CompetitorBusiness3} pattern="[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@%_\+.~#?&//=]*)" onChange={formik.handleChange} _placeholder={{ color: "#7A7A7A", }} /> {formik.errors.CompetitorBusiness3 && formik.touched.CompetitorBusiness3 && ( <Text fontWeight={500} fontSize="sm" color="tomato"> {formik.errors.CompetitorBusiness3} </Text> )} </FormControl> </VStack> </HStack> <HStack pt={"30px"} w={"100%"} alignItems={"start"} gap={"5vw"}> <Box w={"20vw"} pl={"15px"}> <Text fontSize={"22px"} fontWeight={"600"}> {" "} Market Analysis </Text> <Text color={"#7A7A7A"}> Enter details about your decision for this market. </Text> </Box> <VStack w={"100%"} pb={"30px"} borderBottom={"1px solid rgba(2, 43, 58, 0.3)"} > <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>Available Domain</Text> <Tooltip bg={"#fff"} color={"#5F7F9E"} placement="top" hasArrow label="Look up whether your preferred domain is available here for this market and add it here." > <Image src={"/assets/icons/qus_icon.svg"} alt="qus icon" height={21} width={21} /> </Tooltip> </FormLabel> <Input bg={"#fff"} placeholder="Enter company domain" onChange={formik.handleChange} value={formik.values.AvailableDomain} name="AvailableDomain" disabled={_id ? true : false} _placeholder={{ color: "#7A7A7A", }} /> {formik.errors.AvailableDomain && formik.touched.AvailableDomain && ( <Text fontWeight={500} fontSize="sm" color="tomato"> {formik.errors.AvailableDomain} </Text> )} </FormControl> <SimpleGrid w={"100%"} templateColumns="repeat(2 , 1fr)" columns={2} columnGap={4} rowGap={4} > <GridItem colSpan={1} alignSelf={"end"}> <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>Go or No Go?</Text> <Tooltip bg={"#fff"} color={"#5F7F9E"} placement="top" hasArrow label="Go status means you've decided to enter this market. No Go means you concluded this is not a viable market. TBD means this market's status is to be determined. This decision is made after due diligence for this market is complete." > <Image src={"/assets/icons/qus_icon.svg"} alt="qus icon" height={21} width={21} /> </Tooltip> </FormLabel> <Select textTransform={"capitalize"} icon={<AiFillCaretDown />} bg={"#fff"} name="GoOrNoGo" value={formik.values.GoOrNoGo} onChange={formik.handleChange} > <option value="">Select</option> <option value={"GO"}>Go</option> <option value={"NOGO"}>No Go</option> <option value={"TBD"}>TBD</option> </Select> {formik.errors.GoOrNoGo && formik.touched.GoOrNoGo && ( <Text fontWeight={500} fontSize="sm" color="tomato" > {formik.errors.GoOrNoGo} </Text> )} </FormControl> </GridItem> <GridItem colSpan={1}> <FormControl> <FormLabel display={"flex"} justifyContent={"space-between"} > <Text>Low Hanging Fruit?</Text> <Tooltip bg={"#fff"} color={"#5F7F9E"} placement="top" hasArrow label="Low hanging fruit is a market where there is low to no competition and are ideal for ranking quickly. A market is not required to be low hanging fruit, but it is often helpful." > <Image src={"/assets/icons/qus_icon.svg"} alt="qus icon" height={21} width={21} /> </Tooltip> </FormLabel> <Select textTransform={"capitalize"} icon={<AiFillCaretDown />} bg={"#fff"} onChange={formik.handleChange} name="LowHangingFruit" value={formik.values.LowHangingFruit} > <option value={""}>Select</option> <option value={"YES"}>Yes</option> <option value={"NO"}>No </option> <option value={"UNSURE"}>Unsure</option> </Select> {formik.errors.LowHangingFruit && formik.touched.LowHangingFruit && ( <Text fontWeight={500} fontSize="sm" color="tomato" > {formik.errors.LowHangingFruit} </Text> )} </FormControl> </GridItem> </SimpleGrid> </VStack> </HStack> <HStack pt={"20px"} bgColor={"rgba(255, 255, 255, 0.45)"} pr={"40px"} > <HStack pb={"30px"} w={"100%"} alignItems={"start"} gap={"5vw"} > <Box pl={"15px"} w={"20vw"}> <Text fontSize={"22px"} fontWeight={"600"}> Comments </Text> <Text color={"#7A7A7A"}> Add comments about this market that offer additional insights into your due diligence. </Text> </Box> <Box w={"100%"}> <FormControl> <FormLabel>Add comments</FormLabel> <Textarea name="Comments" value={formik.values.Comments} onChange={formik.handleChange} onBlur={formik.handleBlur} placeholder="Add a comment..." color="gray.700" bg={"#fff"} width="100%" _placeholder={{ color: "#7A7A7A", }} /> </FormControl> <Box mt={"12px"} w={"100%"} textAlign={"center"}> {comments.length > 0 && comments.map( ( comment: { userRef: string; comment: string; _id: string; }, index ) => ( <React.Fragment key={index}> <Text>{comment.comment}</Text> <Button mt={"16px"} h={"32px"} w="70px" bg="button.100" color={"#fff"} onClick={() => handleCommentDeletion(comment._id) } > Delete </Button> </React.Fragment> ) )} </Box> </Box> </HStack> </HStack> <HStack bg={"#E1EDEE"} py={"10px"} width={"100%"} justifyContent={"space-between"} borderRadius={"0 15px 15px 15px "} border={"3px solid rgba(174, 214, 220, 0.36)"} alignItems={"flex-end"} > <Flex justifyContent={"left"} align={"center"} gap={"15px"}> <Box> <Button borderRadius={"18px"} padding={"9px 16px"} variant={"solid"} color={"#fff"} bgColor={"button.100"} fontSize={"1.4rem"} fontWeight={"600"} type="submit" > {_id ? "Update" : "Save Location"} </Button> </Box> <Box> <Button borderRadius={"18px"} padding={"9px 16px"} fontSize={"1.2rem"} fontWeight={"700"} colorScheme="button.100" mr={3} variant={"outline"} onClick={onClose} > Close </Button> </Box> </Flex> </HStack> </form> </Box> </VStack> </ModalBody> </ModalContent> </Modal> ); }; export default AddRecordModal;
Editor is loading...
Leave a Comment