Untitled
unknown
plain_text
2 years ago
63 kB
7
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