Автоматизация в интеграциях

This commit is contained in:
Nastya 2025-08-09 02:26:20 +03:00
parent 5e18401762
commit 80de8be287
13 changed files with 456 additions and 103 deletions

File diff suppressed because one or more lines are too long

@ -0,0 +1,19 @@
import { Box, SxProps } from "@mui/material";
export default (sx: SxProps) => (
<Box
component="svg"
width="40px"
height="34px"
viewBox="0 0 40 34"
fill="none"
xmlns="http://www.w3.org/2000/svg"
sx={{
mr: "15px"
}}
>
<path d="M37 0H3C1.34315 0 0 1.34314 0 3V20.967H40V3C40 1.34315 38.6569 0 37 0Z" fill="#9A9AAF" fillOpacity="0.2" />
<path d="M0 24.1244V21.967H40V24.1244C40 25.7813 38.6569 27.1244 37 27.1244H25.5472L26.8066 33.5412H13.2534L14.3928 27.1244H3C1.34315 27.1244 0 25.7813 0 24.1244Z" fill="#9A9AAF" fillOpacity="0.2" />
<path d="M12.9803 9.56785L16.8969 12.7545C17.2236 13.0203 17.7125 12.7878 17.7125 12.3666V10.6689C19.4084 10.3847 22.8834 10.4557 23.2157 13.0131C23.631 16.2098 20.9105 17.2115 20.1629 17.318C19.4153 17.4246 19.7476 18 19.9137 18C22.115 18 25.5 16.2098 25.5 12.8213C25.3505 7.77475 20.246 7.19508 17.7125 7.53607V6.04142C17.7125 5.62197 17.2271 5.38894 16.8998 5.65124L12.9832 8.78983C12.7346 8.98903 12.7332 9.3668 12.9803 9.56785Z" fill="#7E2AEA" fillOpacity="0.5" />
</Box>
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

@ -69,6 +69,8 @@ export type QuizTheme =
export enum QuizMetricType { export enum QuizMetricType {
yandex = "yandexMetricsNumber", yandex = "yandexMetricsNumber",
vk = "vkMetricsNumber", vk = "vkMetricsNumber",
zapier = "zapierIntegration",
postback = "postbackIntegration",
} }
export type FormContactFieldName = "name" | "email" | "phone" | "text" | "address"; export type FormContactFieldName = "name" | "email" | "phone" | "text" | "address";

@ -0,0 +1,75 @@
import { FC } from "react";
import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { Quiz } from "@/model/quiz/quiz";
type PostbackModalProps = {
isModalOpen: boolean;
handleCloseModal: () => void;
companyName: string | null;
quiz: Quiz;
};
export const PostbackModal: FC<PostbackModalProps> = ({
isModalOpen,
handleCloseModal,
companyName,
quiz
}) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
return (
<Dialog
open={isModalOpen}
onClose={handleCloseModal}
fullWidth
PaperProps={{
sx: {
maxWidth: isTablet ? "100%" : "919px",
height: "658px",
borderRadius: "12px",
},
}}
>
<Box>
<Box
sx={{
width: "100%",
height: "68px",
backgroundColor: theme.palette.background.default,
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "0 20px",
}}
>
<Typography
sx={{
fontSize: isMobile ? "20px" : "24px",
fontWeight: "500",
color: theme.palette.grey2.main,
}}
>
Интеграция с {companyName ? companyName : "Postback"}
</Typography>
<IconButton onClick={handleCloseModal}>
<CloseIcon />
</IconButton>
</Box>
<Box
sx={{
padding: "20px",
height: "calc(100% - 68px)",
overflow: "auto",
}}
>
<Typography variant="body1">
Интеграция с Postback находится в разработке.
</Typography>
</Box>
</Box>
</Dialog>
);
};

@ -0,0 +1,75 @@
import { FC } from "react";
import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
import { Quiz } from "@/model/quiz/quiz";
type ZapierModalProps = {
isModalOpen: boolean;
handleCloseModal: () => void;
companyName: string | null;
quiz: Quiz;
};
export const ZapierModal: FC<ZapierModalProps> = ({
isModalOpen,
handleCloseModal,
companyName,
quiz
}) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
return (
<Dialog
open={isModalOpen}
onClose={handleCloseModal}
fullWidth
PaperProps={{
sx: {
maxWidth: isTablet ? "100%" : "919px",
height: "658px",
borderRadius: "12px",
},
}}
>
<Box>
<Box
sx={{
width: "100%",
height: "68px",
backgroundColor: theme.palette.background.default,
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "0 20px",
}}
>
<Typography
sx={{
fontSize: isMobile ? "20px" : "24px",
fontWeight: "500",
color: theme.palette.grey2.main,
}}
>
Интеграция с {companyName ? companyName : "Zapier"}
</Typography>
<IconButton onClick={handleCloseModal}>
<CloseIcon />
</IconButton>
</Box>
<Box
sx={{
padding: "20px",
height: "calc(100% - 68px)",
overflow: "auto",
}}
>
<Typography variant="body1">
Интеграция с Zapier находится в разработке.
</Typography>
</Box>
</Box>
</Dialog>
);
};

@ -27,6 +27,8 @@ export const IntegrationsPage = ({
>(null); >(null);
const [isAmoCrmModalOpen, setIsAmoCrmModalOpen] = useState<boolean>(false); const [isAmoCrmModalOpen, setIsAmoCrmModalOpen] = useState<boolean>(false);
const [isZapierModalOpen, setIsZapierModalOpen] = useState<boolean>(false);
const [isPostbackModalOpen, setIsPostbackModalOpen] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
if (editQuizId === null) navigate("/list"); if (editQuizId === null) navigate("/list");
@ -44,6 +46,12 @@ export const IntegrationsPage = ({
const handleCloseAmoSRMModal = () => { const handleCloseAmoSRMModal = () => {
setIsAmoCrmModalOpen(false); setIsAmoCrmModalOpen(false);
}; };
const handleCloseZapierModal = () => {
setIsZapierModalOpen(false);
};
const handleClosePostbackModal = () => {
setIsPostbackModalOpen(false);
};
return ( return (
<> <>
@ -60,7 +68,7 @@ export const IntegrationsPage = ({
> >
<Typography <Typography
variant="h5" variant="h5"
sx={{ marginBottom: "40px", color: "#333647" }} sx={{ marginBottom: "40px", color: theme.palette.grey3.main }}
> >
Интеграции Интеграции
</Typography> </Typography>
@ -73,6 +81,12 @@ export const IntegrationsPage = ({
setIsAmoCrmModalOpen={setIsAmoCrmModalOpen} setIsAmoCrmModalOpen={setIsAmoCrmModalOpen}
isAmoCrmModalOpen={isAmoCrmModalOpen} isAmoCrmModalOpen={isAmoCrmModalOpen}
handleCloseAmoSRMModal={handleCloseAmoSRMModal} handleCloseAmoSRMModal={handleCloseAmoSRMModal}
setIsZapierModalOpen={setIsZapierModalOpen}
isZapierModalOpen={isZapierModalOpen}
handleCloseZapierModal={handleCloseZapierModal}
setIsPostbackModalOpen={setIsPostbackModalOpen}
isPostbackModalOpen={isPostbackModalOpen}
handleClosePostbackModal={handleClosePostbackModal}
/> />
</Box> </Box>
</> </>

@ -1,13 +1,13 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import { Box, Typography, useTheme } from "@mui/material";
import React, { FC, lazy, Suspense } from "react"; import React, { FC, lazy, Suspense } from "react";
import { ServiceButton } from "./ServiceButton/ServiceButton"; import { ServiceButton } from "./buttons/ServiceButton";
import { ZapierButton } from "./buttons/ZapierButton";
import { PostbackButton } from "./buttons/PostbackButton";
import { YandexMetricaLogo } from "../mocks/YandexMetricaLogo"; import { YandexMetricaLogo } from "../mocks/YandexMetricaLogo";
// import AnalyticsModal from "./AnalyticsModal/AnalyticsModal";
import { VKPixelLogo } from "../mocks/VKPixelLogo"; import { VKPixelLogo } from "../mocks/VKPixelLogo";
import { QuizMetricType } from "@model/quizSettings"; import { QuizMetricType } from "@model/quizSettings";
import { AmoCRMLogo } from "../mocks/AmoCRMLogo"; import { AmoCRMLogo } from "../mocks/AmoCRMLogo";
import { useCurrentQuiz } from "@/stores/quizes/hooks"; import { useCurrentQuiz } from "@/stores/quizes/hooks";
import { useUserStore } from "@/stores/user";
const AnalyticsModal = lazy(() => const AnalyticsModal = lazy(() =>
import("./AnalyticsModal/AnalyticsModal").then((module) => ({ import("./AnalyticsModal/AnalyticsModal").then((module) => ({
@ -21,6 +21,18 @@ const AmoCRMModal = lazy(() =>
})) }))
); );
const ZapierModal = lazy(() =>
import("../IntegrationsModal/Zapier").then((module) => ({
default: module.ZapierModal,
}))
);
const PostbackModal = lazy(() =>
import("../IntegrationsModal/Postback").then((module) => ({
default: module.PostbackModal,
}))
);
type PartnersBoardProps = { type PartnersBoardProps = {
setIsModalOpen: (value: boolean) => void; setIsModalOpen: (value: boolean) => void;
companyName: keyof typeof QuizMetricType | null; companyName: keyof typeof QuizMetricType | null;
@ -30,6 +42,12 @@ type PartnersBoardProps = {
setIsAmoCrmModalOpen: (value: boolean) => void; setIsAmoCrmModalOpen: (value: boolean) => void;
isAmoCrmModalOpen: boolean; isAmoCrmModalOpen: boolean;
handleCloseAmoSRMModal: () => void; handleCloseAmoSRMModal: () => void;
setIsZapierModalOpen: (value: boolean) => void;
isZapierModalOpen: boolean;
handleCloseZapierModal: () => void;
setIsPostbackModalOpen: (value: boolean) => void;
isPostbackModalOpen: boolean;
handleClosePostbackModal: () => void;
}; };
export const PartnersBoard: FC<PartnersBoardProps> = ({ export const PartnersBoard: FC<PartnersBoardProps> = ({
@ -41,13 +59,31 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
setIsAmoCrmModalOpen, setIsAmoCrmModalOpen,
isAmoCrmModalOpen, isAmoCrmModalOpen,
handleCloseAmoSRMModal, handleCloseAmoSRMModal,
setIsZapierModalOpen,
isZapierModalOpen,
handleCloseZapierModal,
setIsPostbackModalOpen,
isPostbackModalOpen,
handleClosePostbackModal,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const user = useUserStore(); const sectionTitleStyles = {
textAlign: { xs: "start", sm: "start", md: "start" } as const,
lineHeight: "1",
marginBottom: "12px",
marginTop: "20px",
color: theme.palette.grey3.main,
fontSize: "18px",
};
const containerStyles = {
display: "flex",
flexWrap: "wrap",
justifyContent: { xs: "start", sm: "start", md: "start" },
gap: { xs: "15px", md: "20px" },
};
return ( return (
<Box <Box
@ -59,24 +95,16 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
}} }}
> >
<Box> <Box>
<>
<Typography <Typography
variant="h6" variant="h6"
sx={{ sx={{
textAlign: { xs: "start", sm: "start", md: "start" }, ...sectionTitleStyles,
lineHeight: "1", marginTop: 0,
marginBottom: "12px",
}} }}
> >
CRM CRM
</Typography> </Typography>
<Box <Box sx={containerStyles}>
sx={{
display: "flex",
flexWrap: "wrap",
justifyContent: { xs: "start", sm: "start", md: "start" },
}}
>
<ServiceButton <ServiceButton
logo={<AmoCRMLogo />} logo={<AmoCRMLogo />}
setIsModalOpen={setIsAmoCrmModalOpen} setIsModalOpen={setIsAmoCrmModalOpen}
@ -84,24 +112,11 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
name={"amoCRM"} name={"amoCRM"}
/> />
</Box> </Box>
</>
<Typography <Typography variant="h6" sx={sectionTitleStyles}>
variant="h6"
sx={{
textAlign: { xs: "start", sm: "start", md: "start" },
lineHeight: "1",
marginBottom: "12px",
}}
>
Аналитика Аналитика
</Typography> </Typography>
<Box <Box sx={containerStyles}>
sx={{
display: "flex",
flexWrap: "wrap",
justifyContent: { xs: "start", sm: "start", md: "start" },
}}
>
<ServiceButton <ServiceButton
logo={<YandexMetricaLogo />} logo={<YandexMetricaLogo />}
setIsModalOpen={setIsModalOpen} setIsModalOpen={setIsModalOpen}
@ -114,9 +129,24 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
name={"vk"} name={"vk"}
setIsModalOpen={setIsModalOpen} setIsModalOpen={setIsModalOpen}
setCompanyName={setCompanyName} setCompanyName={setCompanyName}
></ServiceButton> />
</Box>
<Typography variant="h6" sx={sectionTitleStyles}>
Автоматизация
</Typography>
<Box sx={containerStyles}>
<ZapierButton
setIsModalOpen={setIsZapierModalOpen}
setCompanyName={setCompanyName}
/>
<PostbackButton
setIsModalOpen={setIsPostbackModalOpen}
setCompanyName={setCompanyName}
/>
</Box> </Box>
</Box> </Box>
{companyName && ( {companyName && (
<Suspense> <Suspense>
<AnalyticsModal <AnalyticsModal
@ -132,7 +162,27 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
isModalOpen={isAmoCrmModalOpen} isModalOpen={isAmoCrmModalOpen}
handleCloseModal={handleCloseAmoSRMModal} handleCloseModal={handleCloseAmoSRMModal}
companyName={companyName} companyName={companyName}
quiz={quiz} quiz={quiz!}
/>
</Suspense>
)}
{companyName && isZapierModalOpen && (
<Suspense>
<ZapierModal
isModalOpen={isZapierModalOpen}
handleCloseModal={handleCloseZapierModal}
companyName={companyName}
quiz={quiz!}
/>
</Suspense>
)}
{companyName && isPostbackModalOpen && (
<Suspense>
<PostbackModal
isModalOpen={isPostbackModalOpen}
handleCloseModal={handleClosePostbackModal}
companyName={companyName}
quiz={quiz!}
/> />
</Suspense> </Suspense>
)} )}

@ -1,57 +0,0 @@
import { Box, Typography, useTheme } from "@mui/material";
import { FC } from "react";
import { QuizMetricType } from "@model/quizSettings";
type PartnerItemProps = {
setIsModalOpen: (value: boolean) => void;
setCompanyName: (value: keyof typeof QuizMetricType) => void;
logo?: JSX.Element;
title?: string;
name: string;
};
export const ServiceButton: FC<PartnerItemProps> = ({
setIsModalOpen,
logo,
title,
name,
setCompanyName,
}) => {
const theme = useTheme();
const handleClick = () => {
setCompanyName(name as keyof typeof QuizMetricType);
setIsModalOpen(true);
};
return (
<>
<Box
sx={{
width: 250,
height: 60,
backgroundColor: "white",
borderRadius: "8px",
padding: "0 20px",
display: "flex",
alignItems: "center",
marginBottom: "2%",
marginRight: "2%",
cursor: "pointer",
}}
onClick={handleClick}
>
{logo && logo}
<Typography
sx={{
fontSize: "18px",
fontWeight: "400",
marginLeft: "15px",
}}
>
{title && title}
</Typography>
</Box>
</>
);
};

@ -0,0 +1,53 @@
import { Box } from "@mui/material";
import { FC, useState, ReactNode } from "react";
type IntegrationButtonProps = {
children: ReactNode;
onClick: () => void;
padding?: string;
};
export const IntegrationButton: FC<IntegrationButtonProps> = ({
children,
onClick,
padding = "0 20px",
}) => {
const [isPressed, setIsPressed] = useState(false);
const handleMouseDown = () => setIsPressed(true);
const handleMouseUp = () => setIsPressed(false);
const handleMouseLeave = () => setIsPressed(false);
return (
<Box
sx={{
width: 250,
height: 60,
backgroundColor: "white",
borderRadius: "8px",
padding,
display: "flex",
alignItems: "center",
cursor: "pointer",
transition: "all 0.2s ease",
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
transform: isPressed ? "translateY(1px)" : "translateY(0)",
"&:hover": {
backgroundColor: "#f8f9fa",
boxShadow: "0 4px 8px rgba(0, 0, 0, 0.15)",
transform: "translateY(-1px)",
},
"&:active": {
transform: "translateY(1px)",
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.1)",
},
}}
onClick={onClick}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseLeave}
>
{children}
</Box>
);
};

@ -0,0 +1,32 @@
import { Box } from "@mui/material";
import { FC } from "react";
import { QuizMetricType } from "@model/quizSettings";
import PostbackDefault from "@/assets/icons/logo/Postback";
import PostbackPC from "@/assets/icons/logo/PostbackPC";
import { IntegrationButton } from "./IntegrationButton";
type PostbackButtonProps = {
setIsModalOpen: (value: boolean) => void;
setCompanyName: (value: keyof typeof QuizMetricType) => void;
};
export const PostbackButton: FC<PostbackButtonProps> = ({
setIsModalOpen,
setCompanyName,
}) => {
const handleClick = () => {
setCompanyName("postback" as keyof typeof QuizMetricType);
setIsModalOpen(true);
};
return (
<IntegrationButton onClick={handleClick} padding="0 0 0 20px">
<>
{/* Иконка монитора */}
<PostbackPC />
{/* Текст Postback */}
<PostbackDefault />
</>
</IntegrationButton>
);
};

@ -0,0 +1,40 @@
import { Typography } from "@mui/material";
import { FC } from "react";
import { QuizMetricType } from "@model/quizSettings";
import { IntegrationButton } from "./IntegrationButton";
type PartnerItemProps = {
setIsModalOpen: (value: boolean) => void;
setCompanyName: (value: keyof typeof QuizMetricType) => void;
logo?: JSX.Element;
title?: string;
name: string;
};
export const ServiceButton: FC<PartnerItemProps> = ({
setIsModalOpen,
logo,
title,
name,
setCompanyName,
}) => {
const handleClick = () => {
setCompanyName(name as keyof typeof QuizMetricType);
setIsModalOpen(true);
};
return (
<IntegrationButton onClick={handleClick}>
{logo && logo}
<Typography
sx={{
fontSize: "18px",
fontWeight: "400",
marginLeft: "15px",
}}
>
{title && title}
</Typography>
</IntegrationButton>
);
};

@ -0,0 +1,35 @@
import { Box } from "@mui/material";
import { FC } from "react";
import { QuizMetricType } from "@model/quizSettings";
import zapierLogo from "@/assets/icons/logo/zapier.png";
import { IntegrationButton } from "./IntegrationButton";
type ZapierButtonProps = {
setIsModalOpen: (value: boolean) => void;
setCompanyName: (value: keyof typeof QuizMetricType) => void;
};
export const ZapierButton: FC<ZapierButtonProps> = ({
setIsModalOpen,
setCompanyName,
}) => {
const handleClick = () => {
setCompanyName("zapier" as keyof typeof QuizMetricType);
setIsModalOpen(true);
};
return (
<IntegrationButton onClick={handleClick} padding="14px 128px 14px 20px">
<Box
component="img"
src={zapierLogo}
alt="Zapier"
sx={{
width: "103px",
height: "33px",
objectFit: "contain",
}}
/>
</IntegrationButton>
);
};