Merge branch '26' into dev

This commit is contained in:
Nastya 2024-05-25 22:15:15 +03:00
commit eb3c8b960a
37 changed files with 1206 additions and 677 deletions

332
src/api/integration.ts Normal file

@ -0,0 +1,332 @@
import { makeRequest } from "@api/makeRequest";
import { parseAxiosError } from "@utils/parse-error";
export type PaginationRequest = {
page: number;
size: number;
};
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/amocrm`;
// получение информации об аккаунте
export type AccountResponse = {
ID: number;
AccountID: string;
AmoID: number;
Name: string;
Email: string;
Role: string;
Group: number;
Deleted: boolean;
CreatedAt: number;
Subdomain: string;
Amoiserid: number;
Country: string;
};
export const getAccount = async (): Promise<
[AccountResponse | null, string?]
> => {
try {
const response = await makeRequest<void, AccountResponse>({
method: "GET",
url: `${API_URL}/account`,
useToken: true,
});
return [response];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить информацию об аккаунте. ${error}`];
}
};
// подключить Amo
export const connectAmo = async (): Promise<[string | null, string?]> => {
try {
const response = await makeRequest<void, { link: string }>({
method: "POST",
url: `${API_URL}/account`,
useToken: true,
withCredentials: true,
});
return [response.link];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось подключить аккаунт. ${error}`];
}
};
// получение токена
export type TokenPair = {
accessToken: string;
refreshToken: string;
};
export const getTokens = async (): Promise<[TokenPair | null, string?]> => {
try {
const response = await makeRequest<void, TokenPair>({
method: "GET",
url: `${API_URL}/webhook/create`,
useToken: true,
});
return [response];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Failed to get tokens. ${error}`];
}
};
//получение списка тегов
export type Tag = {
ID: number;
AmoID: number;
AccountID: number;
Entity: string;
Name: string;
Color: string;
Deleted: boolean;
CreatedAt: number;
};
export type TagsResponse = {
count: number;
items: Tag[];
};
export const getTags = async ({
page,
size,
}: PaginationRequest): Promise<[TagsResponse | null, string?]> => {
try {
const tagsResponse = await makeRequest<PaginationRequest, TagsResponse>({
method: "GET",
url: `${API_URL}/tags?page=${page}&size=${size}`,
});
return [tagsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список тегов. ${error}`];
}
};
//получение списка пользователей
export type User = {
ID: number;
AccountID: string;
AmoID: number;
Name: string;
Email: string;
Role: string;
Group: number;
Deleted: boolean;
CreatedAt: number;
Subdomain: string;
Amoiserid: number;
Country: string;
};
export type UsersResponse = {
count: number;
items: User[];
};
export const getUsers = async ({
page,
size,
}: PaginationRequest): Promise<[UsersResponse | null, string?]> => {
try {
const usersResponse = await makeRequest<PaginationRequest, UsersResponse>({
method: "GET",
url: `${API_URL}/users?page=${page}&size=${size}`,
});
return [usersResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список пользователей. ${error}`];
}
};
//получение списка шагов
export type Step = {
ID: number;
AmoID: number;
PipelineID: number;
AccountID: number;
Name: string;
Color: string;
Deleted: boolean;
CreatedAt: number;
};
export type StepsResponse = {
count: number;
items: Step[];
};
export const getSteps = async ({
page,
size,
pipelineId,
}: PaginationRequest & { pipelineId: number }): Promise<
[StepsResponse | null, string?]
> => {
try {
const stepsResponse = await makeRequest<
PaginationRequest & { pipelineId: number },
StepsResponse
>({
method: "GET",
url: `${API_URL}/steps?page=${page}&size=${size}&pipelineID=${pipelineId}`,
});
return [stepsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список шагов. ${error}`];
}
};
//получение списка воронок
export type Pipeline = {
ID: number;
AmoID: number;
AccountID: number;
Name: string;
IsArchive: boolean;
Deleted: boolean;
CreatedAt: number;
};
export type PipelinesResponse = {
count: number;
items: Pipeline[];
};
export const getPipelines = async ({
page,
size,
}: PaginationRequest): Promise<[PipelinesResponse | null, string?]> => {
try {
const pipelinesResponse = await makeRequest<
PaginationRequest,
PipelinesResponse
>({
method: "GET",
url: `${API_URL}/pipelines?page=${page}&size=${size}`,
});
return [pipelinesResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список воронок. ${error}`];
}
};
//получение настроек интеграции
export type IntegrationRules = {
ID: number;
AccountID: number;
QuizID: number;
PerformerID: number;
PipelineID: number;
StepID: number;
UTMs: number[];
FieldsRule: {
lead: { QuestionID: number }[];
contact: { ContactRuleMap: string }[];
company: { QuestionID: number }[];
customer: { QuestionID: number }[];
};
Deleted: boolean;
CreatedAt: number;
};
export const getIntegrationRules = async (
quizID: string,
): Promise<[IntegrationRules | null, string?]> => {
try {
const settingsResponse = await makeRequest<void, IntegrationRules>({
method: "GET",
url: `${API_URL}/rules/${quizID}`,
});
return [settingsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить настройки интеграции. ${error}`];
}
};
//обновление настроек интеграции
export type IntegrationRulesUpdate = {
PerformerID: number;
PipelineID: number;
StepID: number;
Utms: number[];
Fieldsrule: {
Lead: { QuestionID: number }[];
Contact: { ContactRuleMap: string }[];
Company: { QuestionID: number }[];
Customer: { QuestionID: number }[];
};
};
export const updateIntegrationRules = async (
quizID: string,
settings: IntegrationRulesUpdate,
): Promise<[string | null, string?]> => {
try {
const updateResponse = await makeRequest<IntegrationRulesUpdate, string>({
method: "PATCH",
url: `${API_URL}/rules/${quizID}`,
body: settings,
});
return [updateResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Failed to update integration settings. ${error}`];
}
};
// Получение кастомных полей
export type CustomField = {
ID: number;
AmoID: number;
Code: string;
AccountID: number;
Name: string;
EntityType: string;
Type: string;
Deleted: boolean;
CreatedAt: number;
};
export type CustomFieldsResponse = {
count: number;
items: CustomField[];
};
export const getCustomFields = async (
pagination: PaginationRequest,
): Promise<[CustomFieldsResponse | null, string?]> => {
try {
const fieldsResponse = await makeRequest<
PaginationRequest,
CustomFieldsResponse
>({
method: "GET",
url: `${API_URL}/fields?page=${pagination.page}&size=${pagination.size}`,
});
return [fieldsResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить список кастомных полей. ${error}`];
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

@ -0,0 +1,51 @@
import { Button, Typography, useMediaQuery, useTheme } from "@mui/material";
import AmoLogo from "../../assets/icons/Amologo.png";
import { FC } from "react";
type AmoButtonProps = {
onClick?: () => void;
};
export const AmoButton: FC<AmoButtonProps> = ({ onClick }) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
return (
<Button
style={{
backgroundColor: "#329dc9",
width: isMobile ? "270px" : "310px",
height: "47px",
display: "inline-flex",
justifyContent: "start",
border: "#316a88 1px solid",
color: "white",
textDecoration: "none",
cursor: "pointer",
}}
onClick={onClick}
>
<img
src={AmoLogo}
style={{
height: "100%",
maxWidth: "339px",
objectFit: "scale-down",
userSelect: "none",
pointerEvents: "none",
}}
alt={"AmoCRM"}
/>
<Typography
sx={{
margin: "auto",
letterSpacing: "1px",
fontSize: "14px",
fontWeight: 600,
textTransform: "uppercase",
}}
>
Подключить
</Typography>
</Button>
);
};

@ -1,33 +1,248 @@
import * as React from "react"; import * as React from "react";
import { FC } from "react"; import { FC, useEffect, useMemo, useRef, useState } from "react";
import Radio from "@mui/material/Radio"; import Radio from "@mui/material/Radio";
import RadioGroup from "@mui/material/RadioGroup"; import RadioGroup from "@mui/material/RadioGroup";
import FormControlLabel from "@mui/material/FormControlLabel"; import FormControlLabel from "@mui/material/FormControlLabel";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import CheckboxIcon from "@icons/Checkbox"; import CheckboxIcon from "@icons/Checkbox";
import { useTheme } from "@mui/material"; import { Typography, useTheme } from "@mui/material";
import {
getPipelines,
getSteps,
getTags,
PaginationRequest,
Pipeline,
Step,
Tag,
} from "@api/integration";
type CustomRadioGroupProps = { type CustomRadioGroupProps = {
items: string[]; type?: string;
selectedValue: string | null; selectedValue: string | null;
setSelectedValue: (value: string | null) => void; setSelectedValue: (value: string | null) => void;
pipelineId?: number | null;
}; };
const SIZE = 25;
export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
items, type,
selectedValue, selectedValue,
setSelectedValue, setSelectedValue,
pipelineId,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const [currentValue, setCurrentValue] = React.useState<string | null>( const [currentValue, setCurrentValue] = useState<string | null>(
selectedValue, selectedValue,
); );
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(false);
const [tags, setTags] = useState<Tag[]>([]);
const [steps, setSteps] = useState<Step[]>([]);
const [pipelines, setPipelines] = useState<Pipeline[]>([]);
const [hasMoreItems, setHasMoreItems] = useState(true);
const boxRef = useRef(null);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedValue((event.target as HTMLInputElement).value); setSelectedValue((event.target as HTMLInputElement).value);
setCurrentValue((event.target as HTMLInputElement).value); setCurrentValue((event.target as HTMLInputElement).value);
}; };
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const scrollHeight = e.currentTarget.scrollHeight;
const scrollTop = e.currentTarget.scrollTop;
const clientHeight = e.currentTarget.clientHeight;
const scrolledToBottom = scrollTop / (scrollHeight - clientHeight) > 0.9;
if (scrolledToBottom && !isLoading && hasMoreItems) {
setPage((prevPage) => prevPage + 1);
}
};
useEffect(() => {
if (type === "typeTags" && hasMoreItems) {
setIsLoading(true);
const pagination: PaginationRequest = {
page: page,
size: SIZE,
};
getTags(pagination).then(([response]) => {
if (response && response.items !== null) {
setTags((prevItems) => [...prevItems, ...response.items]);
if (response.items.length < SIZE) {
setHasMoreItems(false);
}
}
setIsLoading(false);
});
}
if (type === "typeSteps" && hasMoreItems && pipelineId) {
setIsLoading(true);
const pagination: PaginationRequest & { pipelineId: number } = {
page: page,
size: SIZE,
pipelineId: pipelineId,
};
getSteps(pagination).then(([response]) => {
if (response && response.items !== null) {
setSteps((prevItems) => [...prevItems, ...response.items]);
if (response.items.length < SIZE) {
setHasMoreItems(false);
}
}
setIsLoading(false);
});
}
if (type === "typePipelines" && hasMoreItems) {
setIsLoading(true);
const pagination: PaginationRequest = {
page: page,
size: SIZE,
};
getPipelines(pagination).then(([response]) => {
if (response && response.items !== null) {
setPipelines((prevItems) => [...prevItems, ...response.items]);
if (response.items.length < SIZE) {
setHasMoreItems(false);
}
}
setIsLoading(false);
});
}
}, [page, type, hasMoreItems, pipelineId]);
const formControlLabels = useMemo(() => {
if (type === "typeTags" && tags && tags.length !== 0) {
return tags.map((item) => (
<FormControlLabel
key={item.ID}
sx={{
color: "black",
padding: "15px",
borderBottom: `1px solid ${theme.palette.background.default}`,
display: "flex",
justifyContent: "space-between",
borderRadius: "12px",
margin: 0,
backgroundColor:
currentValue === item.Name
? theme.palette.background.default
: theme.palette.common.white,
}}
value={item.Name}
control={
<Radio
checkedIcon={
<CheckboxIcon
checked
isRounded
color={theme.palette.brightPurple.main}
/>
}
icon={<CheckboxIcon isRounded />}
/>
}
label={
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography sx={{ color: `${item.Color}` }}>
{item.Name}
</Typography>
<Typography>{item.Entity}</Typography>
</Box>
}
labelPlacement={"start"}
/>
));
}
if (type === "typeSteps" && steps && steps.length !== 0) {
return steps.map((step) => (
<FormControlLabel
key={step.ID}
sx={{
color: "black",
padding: "15px",
borderBottom: `1px solid ${theme.palette.background.default}`,
display: "flex",
justifyContent: "space-between",
borderRadius: "12px",
margin: 0,
backgroundColor:
currentValue === step.Name
? theme.palette.background.default
: theme.palette.common.white,
}}
value={step.Name}
control={
<Radio
checkedIcon={
<CheckboxIcon
checked
isRounded
color={theme.palette.brightPurple.main}
/>
}
icon={<CheckboxIcon isRounded />}
/>
}
label={step.Name}
labelPlacement={"start"}
/>
));
}
if (type === "typePipelines" && pipelines && pipelines.length !== 0) {
return pipelines.map((pipeline) => (
<FormControlLabel
key={pipeline.ID}
sx={{
color: "black",
padding: "15px",
borderBottom: `1px solid ${theme.palette.background.default}`,
display: "flex",
justifyContent: "space-between",
borderRadius: "12px",
margin: 0,
backgroundColor:
currentValue === pipeline.Name
? theme.palette.background.default
: theme.palette.common.white,
}}
value={pipeline.Name}
control={
<Radio
checkedIcon={
<CheckboxIcon
checked
isRounded
color={theme.palette.brightPurple.main}
/>
}
icon={<CheckboxIcon isRounded />}
/>
}
label={pipeline.Name}
labelPlacement={"start"}
/>
));
}
return (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100%",
padding: "15px",
}}
>
<Typography>Нет элементов</Typography>
</Box>
);
}, [tags, steps, currentValue, type, pipelines]);
return ( return (
<Box <Box
ref={boxRef}
onScroll={handleScroll}
sx={{ sx={{
border: `1px solid ${theme.palette.grey2.main}`, border: `1px solid ${theme.palette.grey2.main}`,
borderRadius: "12px", borderRadius: "12px",
@ -42,39 +257,7 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
value={currentValue} value={currentValue}
onChange={handleChange} onChange={handleChange}
> >
{items.map((item) => ( {formControlLabels}
<FormControlLabel
key={item}
sx={{
color: "black",
padding: "15px",
borderBottom: `1px solid ${theme.palette.background.default}`,
display: "flex",
justifyContent: "space-between",
borderRadius: "12px",
margin: 0,
backgroundColor:
currentValue === item
? theme.palette.background.default
: theme.palette.common.white,
}}
value={item}
control={
<Radio
checkedIcon={
<CheckboxIcon
checked
isRounded
color={theme.palette.brightPurple.main}
/>
}
icon={<CheckboxIcon isRounded />}
/>
}
label={item}
labelPlacement={"start"}
/>
))}
</RadioGroup> </RadioGroup>
</Box> </Box>
); );

@ -8,40 +8,151 @@ import {
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import { FC, useCallback, useRef, useState } from "react"; import * as React from "react";
import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import "./CustomSelect.css"; import "./CustomSelect.css";
import arrow_down from "../../assets/icons/arrow_down.svg"; import arrow_down from "../../assets/icons/arrow_down.svg";
import { getUsers, PaginationRequest, User } from "@api/integration";
type CustomSelectProps = { type CustomSelectProps = {
items: string[];
selectedItem: string | null; selectedItem: string | null;
setSelectedItem: (value: string | null) => void; setSelectedItem: (value: string | null) => void;
type?: string;
}; };
export const CustomSelect: FC<CustomSelectProps> = ({ export const CustomSelect: FC<CustomSelectProps> = ({
items,
selectedItem, selectedItem,
setSelectedItem, setSelectedItem,
type,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const [opened, setOpened] = useState<boolean>(false); const [opened, setOpened] = useState<boolean>(false);
const [currentValue, setCurrentValue] = useState<string | null>(selectedItem); const [currentValue, setCurrentValue] = useState<string | null>(selectedItem);
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [page, setPage] = useState(1);
const [hasMoreItems, setHasMoreItems] = useState(true);
const SIZE = 25;
const ref = useRef<HTMLDivElement | null>(null); const ref = useRef<HTMLDivElement | null>(null);
const selectWidth = ref.current ? ref.current.offsetWidth : undefined;
const [savedValue, setSavedValue] = useState<number | null>(null);
const onSelectItem = useCallback( const onSelectItem = useCallback(
(event: SelectChangeEvent<HTMLDivElement>) => { (event: SelectChangeEvent<HTMLDivElement>) => {
const newValue = event.target.value.toString(); const newValue = event.target.value.toString();
const selectedUser = users.find((user) => user.Name === newValue);
if (selectedUser) {
//для сохранения ID выбранного пользователя в стейт или конфиг...
setSavedValue(selectedUser.ID);
}
setCurrentValue(newValue); setCurrentValue(newValue);
setSelectedItem(newValue); setSelectedItem(newValue);
}, },
[setSelectedItem, setCurrentValue], [setSelectedItem, setCurrentValue, setSavedValue, users],
); );
const toggleOpened = useCallback(() => { const toggleOpened = useCallback(() => {
setOpened((isOpened) => !isOpened); setOpened((isOpened) => !isOpened);
}, []); }, []);
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const scrollHeight = e.currentTarget.scrollHeight;
const scrollTop = e.currentTarget.scrollTop;
const clientHeight = e.currentTarget.clientHeight;
const scrolledToBottom = scrollTop / (scrollHeight - clientHeight) > 0.9;
if (scrolledToBottom && !isLoading && hasMoreItems) {
setPage((prevPage) => prevPage + 1);
}
};
useEffect(() => {
if (type === "typeUsers" && hasMoreItems) {
setIsLoading(true);
const pagination: PaginationRequest = {
page: page,
size: SIZE,
};
getUsers(pagination).then(([response]) => {
if (response && response.items !== null) {
setUsers((prevItems) => [...prevItems, ...response.items]);
if (response.items.length < SIZE) {
setHasMoreItems(false);
}
}
setIsLoading(false);
});
}
}, [page, type, hasMoreItems]);
const menuItems = useMemo(() => {
if (type === "typeUsers" && users && users.length !== 0) {
return users.map((user) => (
<MenuItem
key={user.ID}
value={user.Name}
sx={{
padding: "6px 0",
zIndex: 2,
borderTop: "1px solid rgba(154, 154, 175, 0.1)",
width: "auto",
}}
>
<Box
sx={{
fontSize: "16px",
color: "#4D4D4D",
display: "flex",
flexDirection: isMobile ? "column" : "row",
justifyContent: "space-evenly",
width: "100%",
}}
>
<Typography
sx={{
width: "33%",
borderRight: isMobile
? "none"
: "1px solid rgba(154, 154, 175, 0.1)",
padding: isMobile ? "5px 0 5px 20px" : "10px 0 10px 20px",
}}
>
{user.Name}
</Typography>
<Typography
sx={{
width: "33%",
borderRight: isMobile
? "none"
: "1px solid rgba(154, 154, 175, 0.1)",
padding: isMobile ? "5px 0 5px 20px" : "10px 0 10px 20px",
color: isMobile ? "#9A9AAF" : "#4D4D4D",
}}
>
{user.Email}
</Typography>
<Typography
sx={{
width: "33%",
padding: isMobile ? "5px 0 5px 20px" : "10px 0 10px 20px",
color: isMobile ? "#9A9AAF" : "#4D4D4D",
}}
>
{user.Role}
</Typography>
</Box>
</MenuItem>
));
}
return (
<MenuItem key={"-1"} disabled sx={{ padding: "12px", zIndex: 2 }}>
нет данных
</MenuItem>
);
}, [users, type]);
return ( return (
<Box> <Box>
<Box <Box
@ -100,29 +211,21 @@ export const CustomSelect: FC<CustomSelectProps> = ({
MenuProps={{ MenuProps={{
disablePortal: true, disablePortal: true,
PaperProps: { PaperProps: {
onScroll: handleScroll,
style: { style: {
zIndex: 2, zIndex: 2,
maxHeight: "300px", maxHeight: "300px",
overflow: "auto", overflow: "auto",
overflowX: "auto",
maxWidth: selectWidth,
}, },
}, },
}} }}
sx={{ width: "100%" }} sx={{}}
onChange={onSelectItem} onChange={onSelectItem}
onClick={toggleOpened} onClick={toggleOpened}
> >
{items.map((item) => { {menuItems}
const uniqueKey = `${item}-${Date.now()}`;
return (
<MenuItem
key={uniqueKey}
value={item}
sx={{ padding: "12px", zIndex: 2 }}
>
{item}
</MenuItem>
);
})}
</Select> </Select>
</Box> </Box>
); );

@ -114,6 +114,8 @@ export interface QuizConfig {
law?: string; law?: string;
}; };
meta: string; meta: string;
antifraud: boolean;
showfc: boolean;
yandexMetricsNumber: number | undefined; yandexMetricsNumber: number | undefined;
vkMetricsNumber: number | undefined; vkMetricsNumber: number | undefined;
} }
@ -228,6 +230,8 @@ export const defaultQuizConfig: QuizConfig = {
button: "", button: "",
}, },
meta: "", meta: "",
antifraud: true,
showfc: true,
yandexMetricsNumber: undefined, yandexMetricsNumber: undefined,
vkMetricsNumber: undefined, vkMetricsNumber: undefined,
}; };

@ -35,6 +35,7 @@ import {
FieldSettingsDrawerState, FieldSettingsDrawerState,
FormContactFieldName, FormContactFieldName,
} from "@model/quizSettings"; } from "@model/quizSettings";
import CustomizedSwitch from "@ui_kit/CustomSwitch";
const buttons: { key: FormContactFieldName; name: string; desc: string }[] = [ const buttons: { key: FormContactFieldName; name: string; desc: string }[] = [
{ name: "Имя", desc: "Дмитрий", key: "name" }, { name: "Имя", desc: "Дмитрий", key: "name" },
@ -63,6 +64,21 @@ export default function ContactFormPage() {
mt: "67px", mt: "67px",
}} }}
> >
<Box sx={{display: "flex", gap: "20px", alignItems: "center"}}>
<CustomizedSwitch
checked={quiz.config.showfc}
onChange={(e) => {
updateQuiz(quiz.id, (quiz) => {
quiz.config.showfc = e.target.checked;
})
}}
/>
<Typography sx={{fontWeight: 500,
color: theme.palette.grey3.main,}}
>
Показывать форму контактов</Typography>
</Box>
{!quiz?.config.formContact.fields.name.used && {!quiz?.config.formContact.fields.name.used &&
!quiz?.config.formContact.fields.email.used && !quiz?.config.formContact.fields.email.used &&
!quiz?.config.formContact.fields.phone.used && !quiz?.config.formContact.fields.phone.used &&

@ -0,0 +1,70 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import { FC } from "react";
import { AccountResponse } from "@api/integration";
type AmoAccountInfoProps = {
handleNextStep: () => void;
accountInfo: AccountResponse;
};
export const AmoAccountInfo: FC<AmoAccountInfoProps> = ({
handleNextStep,
accountInfo,
}) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const infoItem = (title: string, value: string | number) => (
<Box
sx={{
display: "flex",
flexDirection: isMobile ? "column" : "row",
justifyContent: "space-between",
}}
>
<Box sx={{ width: isMobile ? "100%" : "45%" }}>
<Typography sx={{ color: theme.palette.grey2.main }}>
{title}:
</Typography>
</Box>
<Box sx={{ width: isMobile ? "100%" : "45%" }}>
<Typography>{value}</Typography>
</Box>
</Box>
);
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100%",
flexGrow: 1,
}}
>
<Box
sx={{
marginTop: isMobile ? "20px" : "60px",
display: "flex",
flexDirection: "column",
gap: isMobile ? "10px" : "20px",
}}
>
{infoItem("Amo ID", accountInfo.AmoID)}
{infoItem("Имя аккаунта", accountInfo.Name)}
{infoItem("Email аккаунта", accountInfo.Email)}
{infoItem("Роль", accountInfo.Role)}
{infoItem("Группа пользователя", accountInfo.Group)}
{infoItem("URL профиля пользователя в Amo", accountInfo.Subdomain)}
{infoItem("Страна пользователя", accountInfo.Country)}
</Box>
<StepButtonsBlock
isSmallBtnDisabled={true}
onLargeBtnClick={handleNextStep}
largeBtnText={"Далее"}
/>
</Box>
);
};

@ -5,20 +5,19 @@ import {
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import React, { FC, useMemo, useState } from "react"; import React, { FC, useEffect, useMemo, useState } from "react";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { IntegrationStep1 } from "./IntegrationStep1/IntegrationStep1"; import { AmoLogin } from "./AmoLogin/AmoLogin";
import { IntegrationStep2 } from "./IntegrationStep2/IntegrationStep2"; import { AmoStep2 } from "./AmoStep2/AmoStep2";
import { IntegrationStep3 } from "./IntegrationStep3/IntegrationStep3"; import { AmoStep3 } from "./AmoStep3/AmoStep3";
import { IntegrationStep4 } from "./IntegrationStep4/IntegrationStep4"; import { AmoStep4 } from "./AmoStep4/AmoStep4";
import { IntegrationStep5 } from "./IntegrationStep5/IntegrationStep5"; import { AmoStep6 } from "./IntegrationStep6/AmoStep6";
import { IntegrationStep6 } from "./IntegrationStep6/IntegrationStep6"; import { AmoModalTitle } from "./AmoModalTitle/AmoModalTitle";
import { funnelsMock, performersMock, stagesMock } from "../mocks/MockData"; import { AmoSettingsBlock } from "./SettingsBlock/AmoSettingsBlock";
import File from "@ui_kit/QuizPreview/QuizPreviewQuestionTypes/File"; import { AmoStep7 } from "./IntegrationStep7/AmoStep7";
import { IntegrationsModalTitle } from "./IntegrationsModalTitle/IntegrationsModalTitle"; import { AmoAccountInfo } from "./AmoAccountInfo/AmoAccountInfo";
import { SettingsBlock } from "./SettingsBlock/SettingsBlock"; import { AccountResponse, getAccount } from "@api/integration";
import { IntegrationStep7 } from "./IntegrationStep7/IntegrationStep7";
export type TitleKeys = "contacts" | "company" | "deal" | "users" | "buyers"; export type TitleKeys = "contacts" | "company" | "deal" | "users" | "buyers";
@ -32,7 +31,7 @@ type IntegrationsModalProps = {
export type TagKeys = "contact" | "company" | "deal" | "buyer"; export type TagKeys = "contact" | "company" | "deal" | "buyer";
export type TTags = Record<TagKeys, string[] | []>; export type TTags = Record<TagKeys, string[] | []>;
export const IntegrationsModal: FC<IntegrationsModalProps> = ({ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
isModalOpen, isModalOpen,
handleCloseModal, handleCloseModal,
companyName, companyName,
@ -43,18 +42,19 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
const [step, setStep] = useState<number>(0); const [step, setStep] = useState<number>(0);
const [isSettingsBlock, setIsSettingsBlock] = useState<boolean>(false); const [isSettingsBlock, setIsSettingsBlock] = useState<boolean>(false);
const [selectedFunnelPerformer, setSelectedFunnelPerformer] = useState<
const [accountInfo, setAccountInfo] = useState<AccountResponse | null>(null);
const [selectedPipelinePerformer, setSelectedPipelinePerformer] = useState<
string | null string | null
>(null); >(null);
const [selectedFunnel, setSelectedFunnel] = useState<string | null>(null); const [selectedPipeline, setSelectedPipeline] = useState<string | null>(null);
const [selectedStagePerformer, setSelectedStagePerformer] = useState< const [selectedStepsPerformer, setSelectedStepsPerformer] = useState<
string | null string | null
>(null); >(null);
const [selectedStage, setSelectedStage] = useState<string | null>(null); const [selectedStep, setSelectedStep] = useState<string | null>(null);
const [selectedDealPerformer, setSelectedDealPerformer] = useState< const [selectedDealPerformer, setSelectedDealPerformer] = useState<
string | null string | null
>(null); >(null);
const [utmFile, setUtmFile] = useState<File | null>(null);
const [questionEntity, setQuestionEntity] = useState<TQuestionEntity>({ const [questionEntity, setQuestionEntity] = useState<TQuestionEntity>({
contacts: [], contacts: [],
company: [], company: [],
@ -69,6 +69,20 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
buyer: [], buyer: [],
}); });
useEffect(() => {
if (isModalOpen) {
const fetchAccount = async () => {
const [account, error] = await getAccount();
if (account && !error) {
setAccountInfo(account);
} else {
setAccountInfo(null);
}
};
fetchAccount();
}
}, [isModalOpen]);
const handleNextStep = () => { const handleNextStep = () => {
setStep((prevState) => prevState + 1); setStep((prevState) => prevState + 1);
}; };
@ -79,27 +93,33 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
handleCloseModal(); handleCloseModal();
setStep(1); setStep(1);
}; };
const steps = useMemo( const steps = useMemo(
() => [ () => [
{ {
title: "Авторизация в аккаунте", title: accountInfo
? "Информация об аккаунте"
: "Авторизация в аккаунте",
isSettingsAvailable: false, isSettingsAvailable: false,
component: <IntegrationStep1 handleNextStep={handleNextStep} />, component: accountInfo ? (
<AmoAccountInfo
handleNextStep={handleNextStep}
accountInfo={accountInfo}
/>
) : (
<AmoLogin handleNextStep={handleNextStep} />
),
}, },
{ {
title: "Выбор воронки", title: "Выбор воронки",
isSettingsAvailable: true, isSettingsAvailable: true,
component: ( component: (
<IntegrationStep2 <AmoStep2
handlePrevStep={handlePrevStep} handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep} handleNextStep={handleNextStep}
selectedFunnelPerformer={selectedFunnelPerformer} selectedPipelinePerformer={selectedPipelinePerformer}
setSelectedFunnelPerformer={setSelectedFunnelPerformer} setSelectedPipelinePerformer={setSelectedPipelinePerformer}
selectedFunnel={selectedFunnel} selectedPipeline={selectedPipeline}
setSelectedFunnel={setSelectedFunnel} setSelectedPipeline={setSelectedPipeline}
performers={performersMock}
funnels={funnelsMock}
/> />
), ),
}, },
@ -107,15 +127,14 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
title: "Выбор этапа воронки", title: "Выбор этапа воронки",
isSettingsAvailable: true, isSettingsAvailable: true,
component: ( component: (
<IntegrationStep3 <AmoStep3
handlePrevStep={handlePrevStep} handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep} handleNextStep={handleNextStep}
selectedStagePerformer={selectedStagePerformer} selectedStepsPerformer={selectedStepsPerformer}
setSelectedStagePerformer={setSelectedStagePerformer} setSelectedStepsPerformer={setSelectedStepsPerformer}
selectedStage={selectedStage} selectedStep={selectedStep}
setSelectedStage={setSelectedStage} setSelectedStep={setSelectedStep}
performers={performersMock} pipelineId={selectedPipeline}
stages={stagesMock}
/> />
), ),
}, },
@ -123,24 +142,11 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
title: "Сделка", title: "Сделка",
isSettingsAvailable: true, isSettingsAvailable: true,
component: ( component: (
<IntegrationStep4 <AmoStep4
handlePrevStep={handlePrevStep} handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep} handleNextStep={handleNextStep}
selectedDealPerformer={selectedDealPerformer} selectedDealPerformer={selectedDealPerformer}
setSelectedDealPerformer={setSelectedDealPerformer} setSelectedDealPerformer={setSelectedDealPerformer}
performers={performersMock}
/>
),
},
{
title: "Добавление utm-меток",
isSettingsAvailable: false,
component: (
<IntegrationStep5
handlePrevStep={handlePrevStep}
handleNextStep={handleNextStep}
utmFile={utmFile}
setUtmFile={setUtmFile}
/> />
), ),
}, },
@ -148,7 +154,7 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
title: "Соотнесение вопросов и сущностей", title: "Соотнесение вопросов и сущностей",
isSettingsAvailable: true, isSettingsAvailable: true,
component: ( component: (
<IntegrationStep6 <AmoStep6
questionEntity={questionEntity} questionEntity={questionEntity}
setQuestionEntity={setQuestionEntity} setQuestionEntity={setQuestionEntity}
handlePrevStep={handlePrevStep} handlePrevStep={handlePrevStep}
@ -160,7 +166,7 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
title: "Добавление тегов", title: "Добавление тегов",
isSettingsAvailable: true, isSettingsAvailable: true,
component: ( component: (
<IntegrationStep7 <AmoStep7
handleSmallBtn={handlePrevStep} handleSmallBtn={handlePrevStep}
handleLargeBtn={handleSave} handleLargeBtn={handleSave}
tags={tags} tags={tags}
@ -171,11 +177,10 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
], ],
[ [
questionEntity, questionEntity,
utmFile, selectedPipelinePerformer,
selectedFunnelPerformer, selectedPipeline,
selectedFunnel, selectedStepsPerformer,
selectedStagePerformer, selectedStep,
selectedStage,
selectedDealPerformer, selectedDealPerformer,
tags, tags,
], ],
@ -188,7 +193,7 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
open={isModalOpen} open={isModalOpen}
onClose={handleCloseModal} onClose={handleCloseModal}
fullWidth fullWidth
fullScreen={isMobile} // fullScreen={isMobile}
PaperProps={{ PaperProps={{
sx: { sx: {
maxWidth: isTablet ? "100%" : "920px", maxWidth: isTablet ? "100%" : "920px",
@ -209,6 +214,7 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
fontSize: isMobile ? "20px" : "24px", fontSize: isMobile ? "20px" : "24px",
fontWeight: "500", fontWeight: "500",
padding: "20px", padding: "20px",
color: theme.palette.grey2.main,
}} }}
> >
Интеграция с {companyName ? companyName : "партнером"} Интеграция с {companyName ? companyName : "партнером"}
@ -238,7 +244,7 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
flexGrow: 1, flexGrow: 1,
}} }}
> >
<IntegrationsModalTitle <AmoModalTitle
step={step} step={step}
steps={steps} steps={steps}
isSettingsBlock={isSettingsBlock} isSettingsBlock={isSettingsBlock}
@ -247,16 +253,15 @@ export const IntegrationsModal: FC<IntegrationsModalProps> = ({
/> />
{isSettingsBlock ? ( {isSettingsBlock ? (
<Box sx={{ flexGrow: 1, width: "100%" }}> <Box sx={{ flexGrow: 1, width: "100%" }}>
<SettingsBlock <AmoSettingsBlock
stepTitles={stepTitles} stepTitles={stepTitles}
setIsSettingsBlock={setIsSettingsBlock} setIsSettingsBlock={setIsSettingsBlock}
setStep={setStep} setStep={setStep}
selectedDealPerformer={selectedDealPerformer} selectedDealPerformer={selectedDealPerformer}
selectedFunnelPerformer={selectedFunnelPerformer} selectedFunnelPerformer={selectedPipelinePerformer}
selectedFunnel={selectedFunnel} selectedFunnel={selectedPipeline}
selectedStagePerformer={selectedStagePerformer} selectedStagePerformer={selectedStepsPerformer}
selectedStage={selectedStage} selectedStage={selectedStep}
utmFile={utmFile}
questionEntity={questionEntity} questionEntity={questionEntity}
tags={tags} tags={tags}
/> />

@ -0,0 +1,150 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { FC } from "react";
import { AmoButton } from "../../../../components/AmoButton/AmoButton";
import { connectAmo } from "@api/integration";
type IntegrationStep1Props = {
handleNextStep: () => void;
};
// interface Values {
// login: string;
// password: string;
// }
//
// const initialValues: Values = {
// login: "",
// password: "",
// };
//
// const validationSchema = object({
// login: string().required("Поле обязательно"),
// password: string().required("Поле обязательно").min(8, "Минимум 8 символов"),
// });
export const AmoLogin: FC<IntegrationStep1Props> = ({ handleNextStep }) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const onAmoClick = async () => {
console.log("Amo click");
const [url, error] = await connectAmo();
if (url && !error) {
window.open(url, "_blank");
}
};
// const formik = useFormik<Values>({
// initialValues,
// validationSchema,
// onSubmit: async (values, formikHelpers) => {
// const loginTrimmed = values.login.trim();
// const passwordTrimmed = values.password.trim();
// try {
// // Simulate a network request
// await new Promise((resolve) => setTimeout(resolve, 2000));
// handleNextStep();
// } catch (error) {
// formikHelpers.setSubmitting(false);
// if (error instanceof Error) {
// formikHelpers.setErrors({
// login: error.message,
// password: error.message,
// });
// }
// }
// },
// });
return (
<Box
// component="form"
// onSubmit={formik.handleSubmit}
// noValidate
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100%",
flexGrow: 1,
}}
>
{/*<Box*/}
{/* sx={{*/}
{/* marginTop: "68px",*/}
{/* width: isMobile ? "100%" : "500px",*/}
{/* display: "flex",*/}
{/* flexDirection: "column",*/}
{/* alignItems: "center",*/}
{/* gap: "15px",*/}
{/* }}*/}
{/*>*/}
{/* <InputTextfield*/}
{/* TextfieldProps={{*/}
{/* value: formik.values.login,*/}
{/* placeholder: "+7 900 000 00 00 или username@penahaub.com",*/}
{/* onBlur: formik.handleBlur,*/}
{/* error: formik.touched.login && Boolean(formik.errors.login),*/}
{/* helperText: formik.touched.login && formik.errors.login,*/}
{/* "data-cy": "login",*/}
{/* }}*/}
{/* onChange={formik.handleChange}*/}
{/* color={theme.palette.background.default}*/}
{/* id="login"*/}
{/* label="Телефон или E-mail"*/}
{/* gap="10px"*/}
{/* />*/}
{/* <PasswordInput*/}
{/* TextfieldProps={{*/}
{/* value: formik.values.password,*/}
{/* placeholder: "Не менее 8 символов",*/}
{/* onBlur: formik.handleBlur,*/}
{/* error: formik.touched.password && Boolean(formik.errors.password),*/}
{/* helperText: formik.touched.password && formik.errors.password,*/}
{/* type: "password",*/}
{/* "data-cy": "password",*/}
{/* }}*/}
{/* onChange={formik.handleChange}*/}
{/* color={theme.palette.background.default}*/}
{/* id="password"*/}
{/* label="Пароль"*/}
{/* gap="10px"*/}
{/* />*/}
{/*</Box>*/}
<Box sx={{ marginTop: "70px", width: isMobile ? "100%" : "500px" }}>
<Typography
sx={{
fontSize: "16px",
fontWeight: "400",
color: theme.palette.grey2.main,
lineHeight: "1",
}}
>
Инструкция
</Typography>
<Typography
sx={{
marginTop: "12px",
fontSize: "18px",
fontWeight: "400",
color: theme.palette.grey3.main,
lineHeight: "1",
}}
>
После нажатия на кнопку - "Подключить", вас переадресует на страницу
подключения интеграции в ваш аккаунт AmoCRM. Пожалуйста, согласитесь
на всё, что мы предлагаем, иначе чуда не случится.
</Typography>
</Box>
<Box sx={{ marginTop: "50px" }}>
<AmoButton onClick={onAmoClick} />
</Box>
{/*<StepButtonsBlock*/}
{/* isSmallBtnDisabled={true}*/}
{/* largeBtnType={"submit"}*/}
{/* // isLargeBtnDisabled={formik.isSubmitting}*/}
{/* largeBtnText={"Войти"}*/}
{/*/>*/}
</Box>
);
};

@ -4,7 +4,7 @@ import GearIcon from "@icons/GearIcon";
import React, { FC, useCallback, useMemo } from "react"; import React, { FC, useCallback, useMemo } from "react";
import AccountSetting from "@icons/AccountSetting"; import AccountSetting from "@icons/AccountSetting";
type IntegrationsModalTitleProps = { type AmoModalTitleProps = {
step: number; step: number;
steps: { title: string; isSettingsAvailable: boolean }[]; steps: { title: string; isSettingsAvailable: boolean }[];
isSettingsBlock?: boolean; isSettingsBlock?: boolean;
@ -12,7 +12,7 @@ type IntegrationsModalTitleProps = {
setStep: (value: number) => void; setStep: (value: number) => void;
}; };
export const IntegrationsModalTitle: FC<IntegrationsModalTitleProps> = ({ export const AmoModalTitle: FC<AmoModalTitleProps> = ({
step, step,
steps, steps,
setIsSettingsBlock, setIsSettingsBlock,

@ -4,31 +4,26 @@ import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect"; import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/CustomRadioGroup"; import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/CustomRadioGroup";
type IntegrationStep3Props = { type AmoStep2Props = {
handlePrevStep: () => void; handlePrevStep: () => void;
handleNextStep: () => void; handleNextStep: () => void;
selectedStagePerformer: string | null; selectedPipelinePerformer: string | null;
setSelectedStagePerformer: (value: string | null) => void; setSelectedPipelinePerformer: (value: string | null) => void;
selectedStage: string | null; selectedPipeline: string | null;
setSelectedStage: (value: string | null) => void; setSelectedPipeline: (value: string | null) => void;
performers: string[];
stages: string[];
}; };
export const IntegrationStep3: FC<IntegrationStep3Props> = ({ export const AmoStep2: FC<AmoStep2Props> = ({
handlePrevStep, handlePrevStep,
handleNextStep, handleNextStep,
selectedStagePerformer, selectedPipelinePerformer,
setSelectedStagePerformer, setSelectedPipelinePerformer,
selectedStage, selectedPipeline,
setSelectedStage, setSelectedPipeline,
performers,
stages,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
return ( return (
<Box <Box
sx={{ sx={{
@ -41,9 +36,9 @@ export const IntegrationStep3: FC<IntegrationStep3Props> = ({
> >
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}> <Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
<CustomSelect <CustomSelect
selectedItem={selectedStagePerformer} selectedItem={selectedPipelinePerformer}
items={performers} setSelectedItem={setSelectedPipelinePerformer}
setSelectedItem={setSelectedStagePerformer} type={"typeUsers"}
/> />
</Box> </Box>
<Box <Box
@ -55,9 +50,9 @@ export const IntegrationStep3: FC<IntegrationStep3Props> = ({
}} }}
> >
<CustomRadioGroup <CustomRadioGroup
items={stages} selectedValue={selectedPipeline}
selectedValue={selectedStage} setSelectedValue={setSelectedPipeline}
setSelectedValue={setSelectedStage} type={"typePipelines"}
/> />
</Box> </Box>
<Box <Box

@ -4,30 +4,29 @@ import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect"; import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/CustomRadioGroup"; import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/CustomRadioGroup";
type IntegrationStep2Props = { type AmoStep3Props = {
handlePrevStep: () => void; handlePrevStep: () => void;
handleNextStep: () => void; handleNextStep: () => void;
selectedFunnelPerformer: string | null; selectedStepsPerformer: string | null;
setSelectedFunnelPerformer: (value: string | null) => void; setSelectedStepsPerformer: (value: string | null) => void;
selectedFunnel: string | null; selectedStep: string | null;
setSelectedFunnel: (value: string | null) => void; setSelectedStep: (value: string | null) => void;
performers: string[]; pipelineId: string | null;
funnels: string[];
}; };
export const IntegrationStep2: FC<IntegrationStep2Props> = ({ export const AmoStep3: FC<AmoStep3Props> = ({
handlePrevStep, handlePrevStep,
handleNextStep, handleNextStep,
selectedFunnelPerformer, selectedStepsPerformer,
setSelectedFunnelPerformer, setSelectedStepsPerformer,
selectedFunnel, selectedStep,
setSelectedFunnel, setSelectedStep,
performers, pipelineId,
funnels,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
return ( return (
<Box <Box
sx={{ sx={{
@ -40,9 +39,9 @@ export const IntegrationStep2: FC<IntegrationStep2Props> = ({
> >
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}> <Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
<CustomSelect <CustomSelect
selectedItem={selectedFunnelPerformer} selectedItem={selectedStepsPerformer}
items={performers} type={"typeUsers"}
setSelectedItem={setSelectedFunnelPerformer} setSelectedItem={setSelectedStepsPerformer}
/> />
</Box> </Box>
<Box <Box
@ -54,9 +53,11 @@ export const IntegrationStep2: FC<IntegrationStep2Props> = ({
}} }}
> >
<CustomRadioGroup <CustomRadioGroup
items={funnels} // @ts-ignore
selectedValue={selectedFunnel} pipelineId={pipelineId}
setSelectedValue={setSelectedFunnel} type={"typeSteps"}
selectedValue={selectedStep}
setSelectedValue={setSelectedStep}
/> />
</Box> </Box>
<Box <Box

@ -3,20 +3,18 @@ import { FC } from "react";
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock"; import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect"; import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
type IntegrationStep4Props = { type AmoStep4Props = {
handlePrevStep: () => void; handlePrevStep: () => void;
handleNextStep: () => void; handleNextStep: () => void;
selectedDealPerformer: string | null; selectedDealPerformer: string | null;
setSelectedDealPerformer: (value: string | null) => void; setSelectedDealPerformer: (value: string | null) => void;
performers: string[];
}; };
export const IntegrationStep4: FC<IntegrationStep4Props> = ({ export const AmoStep4: FC<AmoStep4Props> = ({
handlePrevStep, handlePrevStep,
handleNextStep, handleNextStep,
selectedDealPerformer, selectedDealPerformer,
setSelectedDealPerformer, setSelectedDealPerformer,
performers,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
@ -35,8 +33,8 @@ export const IntegrationStep4: FC<IntegrationStep4Props> = ({
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}> <Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
<CustomSelect <CustomSelect
selectedItem={selectedDealPerformer} selectedItem={selectedDealPerformer}
items={performers}
setSelectedItem={setSelectedDealPerformer} setSelectedItem={setSelectedDealPerformer}
type={"typeUsers"}
/> />
</Box> </Box>
<Box <Box

@ -1,144 +0,0 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { FC } from "react";
import { object, string } from "yup";
import InputTextfield from "@ui_kit/InputTextfield";
import PasswordInput from "@ui_kit/passwordInput";
import { useFormik } from "formik";
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
type IntegrationStep1Props = {
handleNextStep: () => void;
};
interface Values {
login: string;
password: string;
}
const initialValues: Values = {
login: "",
password: "",
};
const validationSchema = object({
login: string().required("Поле обязательно"),
password: string().required("Поле обязательно").min(8, "Минимум 8 символов"),
});
export const IntegrationStep1: FC<IntegrationStep1Props> = ({
handleNextStep,
}) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
const formik = useFormik<Values>({
initialValues,
validationSchema,
onSubmit: async (values, formikHelpers) => {
const loginTrimmed = values.login.trim();
const passwordTrimmed = values.password.trim();
try {
// Simulate a network request
await new Promise((resolve) => setTimeout(resolve, 2000));
handleNextStep();
} catch (error) {
formikHelpers.setSubmitting(false);
if (error instanceof Error) {
formikHelpers.setErrors({
login: error.message,
password: error.message,
});
}
}
},
});
return (
<Box
component="form"
onSubmit={formik.handleSubmit}
noValidate
sx={{
display: "flex",
flexDirection: "column",
alignItems: isMobile ? "start" : "center",
height: "100%",
flexGrow: 1,
}}
>
<Box
sx={{
marginTop: "68px",
width: isMobile ? "100%" : "500px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "15px",
}}
>
<InputTextfield
TextfieldProps={{
value: formik.values.login,
placeholder: "+7 900 000 00 00 или username@penahaub.com",
onBlur: formik.handleBlur,
error: formik.touched.login && Boolean(formik.errors.login),
helperText: formik.touched.login && formik.errors.login,
"data-cy": "login",
}}
onChange={formik.handleChange}
color={theme.palette.background.default}
id="login"
label="Телефон или E-mail"
gap="10px"
/>
<PasswordInput
TextfieldProps={{
value: formik.values.password,
placeholder: "Не менее 8 символов",
onBlur: formik.handleBlur,
error: formik.touched.password && Boolean(formik.errors.password),
helperText: formik.touched.password && formik.errors.password,
type: "password",
"data-cy": "password",
}}
onChange={formik.handleChange}
color={theme.palette.background.default}
id="password"
label="Пароль"
gap="10px"
/>
</Box>
<Box sx={{ marginTop: "30px", width: isMobile ? "100%" : "500px" }}>
<Typography
sx={{
fontSize: "16px",
fontWeight: "400",
color: theme.palette.grey2.main,
lineHeight: "1",
}}
>
Инструкция
</Typography>
<Typography
sx={{
marginTop: "12px",
fontSize: "18px",
fontWeight: "400",
color: theme.palette.grey3.main,
lineHeight: "1",
}}
>
Повседневная практика показывает, что постоянный количественный рост и
сфера нашей активности способствует подготовки и реализации систем
массового участия
</Typography>
</Box>
<StepButtonsBlock
isSmallBtnDisabled={true}
largeBtnType={"submit"}
isLargeBtnDisabled={formik.isSubmitting}
largeBtnText={"Войти"}
/>
</Box>
);
};

@ -1,94 +0,0 @@
import {
Box,
ButtonBase,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import UploadIcon from "@icons/UploadIcon";
import { type DragEvent, FC, useRef, useState } from "react";
type TextFormat = "txt" | "docx";
interface CustomFileUploaderProps {
description?: string;
accept?: TextFormat[];
handleImageChange: (file: File) => void;
}
export const CustomFileUploader: FC<CustomFileUploaderProps> = ({
accept,
description,
handleImageChange,
}) => {
const theme = useTheme();
const dropZone = useRef<HTMLDivElement>(null);
const [ready, setReady] = useState(false);
const isMobile = useMediaQuery(theme.breakpoints.down(700));
const handleDragEnter = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
setReady(true);
};
const handleDrop = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
const file = event.dataTransfer.files[0];
if (!file) return;
handleImageChange(file);
};
const acceptedFormats = accept
? accept.map((format) => "." + format).join(", ")
: "";
return (
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
<input
onChange={(event) => {
const file = event.target.files?.[0];
if (file) handleImageChange(file);
}}
hidden
accept={acceptedFormats || ".jpg, .jpeg, .png , .gif"}
multiple
type="file"
data-cy="upload-image-input"
/>
<Box
onDragOver={(event: DragEvent<HTMLDivElement>) =>
event.preventDefault()
}
onDrop={handleDrop}
ref={dropZone}
sx={{
width: isMobile ? "100%" : "580px",
padding: isMobile ? "33px" : "33px 10px 33px 55px",
display: "flex",
alignItems: "center",
backgroundColor: theme.palette.background.default,
border: `1px solid ${ready ? "red" : theme.palette.grey2.main}`,
borderRadius: "8px",
gap: "55px",
flexDirection: isMobile ? "column" : "row",
}}
onDragEnter={handleDragEnter}
>
<UploadIcon />
<Box>
<Typography sx={{ color: "#9A9AAF", fontWeight: "bold" }}>
Добавить файл
</Typography>
<Typography
sx={{ color: theme.palette.grey2.main, fontSize: "16px" }}
>
{description || "Принимает JPG, PNG, и GIF формат — максимум 5mb"}
</Typography>
</Box>
</Box>
</ButtonBase>
);
};

@ -1,58 +0,0 @@
import React, { FC } from "react";
import Box from "@mui/material/Box";
import { IconButton, Typography, useTheme } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close";
type FileBlockProps = {
file: File | null;
setFile?: (file: File | null) => void;
};
export const FileBlock: FC<FileBlockProps> = ({ setFile, file }) => {
const theme = useTheme();
return (
<Box sx={{ display: "flex", gap: "15px", alignItems: "center" }}>
<Typography
sx={{
fontSize: "18px",
fontWeight: "400",
color: theme.palette.grey3.main,
}}
>
Вы загрузили:
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
backgroundColor: theme.palette.brightPurple.main,
borderRadius: "8px",
padding: setFile ? "5px 5px 5px 14px" : "5px 14px",
gap: "20px",
}}
>
<Typography
sx={{ color: "white", fontSize: "14px", fontWeight: "400" }}
>
{file?.name}
</Typography>
{setFile && (
<IconButton
onClick={() => setFile(null)}
sx={{
backgroundColor: "#864BD9",
borderRadius: "50%",
width: "24px",
height: "24px",
color: "white",
}}
>
<CloseIcon
sx={{ width: "14px", height: "14px", transform: "scale(1.5)" }}
/>
</IconButton>
)}
</Box>
</Box>
);
};

@ -1,52 +0,0 @@
import { Box, useMediaQuery, useTheme } from "@mui/material";
import React, { FC } from "react";
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import File from "@ui_kit/QuizPreview/QuizPreviewQuestionTypes/File";
import { FileBlock } from "./FileBlock/FileBlock";
import { CustomFileUploader } from "./CustomFileUploader/CustomFileUploader";
type IntegrationStep5Props = {
handlePrevStep: () => void;
handleNextStep: () => void;
setUtmFile: (file: File | null) => void;
utmFile: File | null;
};
export const IntegrationStep5: FC<IntegrationStep5Props> = ({
handlePrevStep,
handleNextStep,
utmFile,
setUtmFile,
}) => {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
height: "100%",
flexGrow: 1,
}}
>
<Box sx={{ alignSelf: "start", marginTop: "20px" }}>
{utmFile ? (
<FileBlock file={utmFile} setFile={setUtmFile} />
) : (
<CustomFileUploader
description={"Принимает .txt и .docx формат — максимум 100мб"}
accept={["txt", "docx"]}
handleImageChange={setUtmFile}
/>
)}
</Box>
<StepButtonsBlock
onLargeBtnClick={handleNextStep}
onSmallBtnClick={handlePrevStep}
isLargeBtnDisabled={!utmFile}
/>
</Box>
);
};

@ -9,17 +9,17 @@ import {
} from "react"; } from "react";
import { ItemsSelectionView } from "./ItemsSelectionView/ItemsSelectionView"; import { ItemsSelectionView } from "./ItemsSelectionView/ItemsSelectionView";
import { ItemDetailsView } from "./ItemDetailsView/ItemDetailsView"; import { ItemDetailsView } from "./ItemDetailsView/ItemDetailsView";
import { TitleKeys, TQuestionEntity } from "../IntegrationsModal"; import { TitleKeys, TQuestionEntity } from "../AmoCRMModal";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
type IntegrationStep6Props = { type AmoStep6Props = {
handlePrevStep: () => void; handlePrevStep: () => void;
handleNextStep: () => void; handleNextStep: () => void;
questionEntity: TQuestionEntity; questionEntity: TQuestionEntity;
setQuestionEntity: Dispatch<SetStateAction<TQuestionEntity>>; setQuestionEntity: Dispatch<SetStateAction<TQuestionEntity>>;
}; };
export const IntegrationStep6: FC<IntegrationStep6Props> = ({ export const AmoStep6: FC<AmoStep6Props> = ({
handlePrevStep, handlePrevStep,
handleNextStep, handleNextStep,
questionEntity, questionEntity,

@ -2,12 +2,7 @@ import { Box, Typography, useTheme } from "@mui/material";
import { FC } from "react"; import { FC } from "react";
import { IconBtnAdd } from "./IconBtnAdd/IconBtnAdd"; import { IconBtnAdd } from "./IconBtnAdd/IconBtnAdd";
import { AnswerItem } from "./AnswerItem/AnswerItem"; import { AnswerItem } from "./AnswerItem/AnswerItem";
import { import { TagKeys, TitleKeys, TQuestionEntity, TTags } from "../../AmoCRMModal";
TagKeys,
TitleKeys,
TQuestionEntity,
TTags,
} from "../../IntegrationsModal";
type ItemProps = { type ItemProps = {
title: TitleKeys | TagKeys; title: TitleKeys | TagKeys;

@ -2,7 +2,7 @@ import { Box, useTheme } from "@mui/material";
import { Item } from "../Item/Item"; import { Item } from "../Item/Item";
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock"; import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { FC } from "react"; import { FC } from "react";
import { TQuestionEntity } from "../../IntegrationsModal"; import { TQuestionEntity } from "../../AmoCRMModal";
type TitleKeys = "contacts" | "company" | "deal" | "users" | "buyers"; type TitleKeys = "contacts" | "company" | "deal" | "users" | "buyers";

@ -4,6 +4,7 @@ import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { FC } from "react"; import { FC } from "react";
type ItemsSelectionViewProps = { type ItemsSelectionViewProps = {
type?: string;
items: string[]; items: string[];
selectedValue: string | null; selectedValue: string | null;
setSelectedValue: (value: string | null) => void; setSelectedValue: (value: string | null) => void;
@ -17,6 +18,7 @@ export const ItemsSelectionView: FC<ItemsSelectionViewProps> = ({
setSelectedValue, setSelectedValue,
onLargeBtnClick, onLargeBtnClick,
onSmallBtnClick, onSmallBtnClick,
type,
}) => { }) => {
return ( return (
<Box <Box
@ -37,6 +39,7 @@ export const ItemsSelectionView: FC<ItemsSelectionViewProps> = ({
}} }}
> >
<CustomRadioGroup <CustomRadioGroup
type={type}
items={items} items={items}
selectedValue={selectedValue} selectedValue={selectedValue}
setSelectedValue={setSelectedValue} setSelectedValue={setSelectedValue}

@ -8,19 +8,19 @@ import {
useState, useState,
} from "react"; } from "react";
import { TagKeys, TTags } from "../IntegrationsModal"; import { TagKeys, TTags } from "../AmoCRMModal";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import { ItemsSelectionView } from "../IntegrationStep6/ItemsSelectionView/ItemsSelectionView"; import { ItemsSelectionView } from "../IntegrationStep6/ItemsSelectionView/ItemsSelectionView";
import { TagsDetailsView } from "./TagsDetailsView/TagsDetailsView"; import { TagsDetailsView } from "./TagsDetailsView/TagsDetailsView";
type IntegrationStep7Props = { type AmoStep7Props = {
handleSmallBtn: () => void; handleSmallBtn: () => void;
handleLargeBtn: () => void; handleLargeBtn: () => void;
tags: TTags; tags: TTags;
setTags: Dispatch<SetStateAction<TTags>>; setTags: Dispatch<SetStateAction<TTags>>;
}; };
export const IntegrationStep7: FC<IntegrationStep7Props> = ({ export const AmoStep7: FC<AmoStep7Props> = ({
handleSmallBtn, handleSmallBtn,
handleLargeBtn, handleLargeBtn,
tags, tags,
@ -59,6 +59,7 @@ export const IntegrationStep7: FC<IntegrationStep7Props> = ({
items={items} items={items}
selectedValue={selectedValue} selectedValue={selectedValue}
setSelectedValue={setSelectedValue} setSelectedValue={setSelectedValue}
type={"typeTags"}
onSmallBtnClick={() => { onSmallBtnClick={() => {
setActiveItem(null); setActiveItem(null);
setIsSelection(false); setIsSelection(false);

@ -1,7 +1,7 @@
import { Box, Typography, useTheme } from "@mui/material"; import { Box, Typography, useTheme } from "@mui/material";
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock"; import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
import { FC } from "react"; import { FC } from "react";
import { TagKeys, TTags } from "../../IntegrationsModal"; import { TagKeys, TTags } from "../../AmoCRMModal";
import { Item } from "../../IntegrationStep6/Item/Item"; import { Item } from "../../IntegrationStep6/Item/Item";
type TagsDetailsViewProps = { type TagsDetailsViewProps = {

@ -2,9 +2,9 @@ import { FC } from "react";
import { Box, useMediaQuery, useTheme } from "@mui/material"; import { Box, useMediaQuery, useTheme } from "@mui/material";
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock"; import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
import { SettingItem } from "./SettingItem/SettingItem"; import { SettingItem } from "./SettingItem/SettingItem";
import { TQuestionEntity, TTags } from "../IntegrationsModal"; import { TQuestionEntity, TTags } from "../AmoCRMModal";
type SettingsBlockProps = { type AmoSettingsBlockProps = {
stepTitles: string[]; stepTitles: string[];
setStep: (value: number) => void; setStep: (value: number) => void;
setIsSettingsBlock: (value: boolean) => void; setIsSettingsBlock: (value: boolean) => void;
@ -13,12 +13,11 @@ type SettingsBlockProps = {
selectedStagePerformer: string | null; selectedStagePerformer: string | null;
selectedStage: string | null; selectedStage: string | null;
selectedDealPerformer: string | null; selectedDealPerformer: string | null;
utmFile: File | null;
questionEntity: TQuestionEntity; questionEntity: TQuestionEntity;
tags: TTags; tags: TTags;
}; };
export const SettingsBlock: FC<SettingsBlockProps> = ({ export const AmoSettingsBlock: FC<AmoSettingsBlockProps> = ({
stepTitles, stepTitles,
setStep, setStep,
setIsSettingsBlock, setIsSettingsBlock,
@ -27,7 +26,6 @@ export const SettingsBlock: FC<SettingsBlockProps> = ({
selectedStagePerformer, selectedStagePerformer,
selectedDealPerformer, selectedDealPerformer,
selectedStage, selectedStage,
utmFile,
questionEntity, questionEntity,
tags, tags,
}) => { }) => {
@ -68,7 +66,6 @@ export const SettingsBlock: FC<SettingsBlockProps> = ({
selectedStagePerformer={selectedStagePerformer} selectedStagePerformer={selectedStagePerformer}
selectedDealPerformer={selectedDealPerformer} selectedDealPerformer={selectedDealPerformer}
selectedStage={selectedStage} selectedStage={selectedStage}
utmFile={utmFile}
questionEntity={questionEntity} questionEntity={questionEntity}
tags={tags} tags={tags}
/> />

@ -4,8 +4,7 @@ import { Typography, useMediaQuery, useTheme } from "@mui/material";
import { SettingItemHeader } from "./SettingItemHeader/SettingItemHeader"; import { SettingItemHeader } from "./SettingItemHeader/SettingItemHeader";
import { ResponsiblePerson } from "./ResponsiblePerson/ResponsiblePerson"; import { ResponsiblePerson } from "./ResponsiblePerson/ResponsiblePerson";
import { SelectedParameter } from "./SelectedParameter/SelectedParameter"; import { SelectedParameter } from "./SelectedParameter/SelectedParameter";
import { FileBlock } from "../../IntegrationStep5/FileBlock/FileBlock"; import { TQuestionEntity, TTags } from "../../AmoCRMModal";
import { TQuestionEntity, TTags } from "../../IntegrationsModal";
type SettingItemProps = { type SettingItemProps = {
step: number; step: number;
@ -17,7 +16,6 @@ type SettingItemProps = {
selectedStagePerformer: string | null; selectedStagePerformer: string | null;
selectedDealPerformer: string | null; selectedDealPerformer: string | null;
selectedStage: string | null; selectedStage: string | null;
utmFile: File | null;
questionEntity: TQuestionEntity; questionEntity: TQuestionEntity;
tags: TTags; tags: TTags;
}; };
@ -32,7 +30,6 @@ export const SettingItem: FC<SettingItemProps> = ({
selectedStagePerformer, selectedStagePerformer,
selectedDealPerformer, selectedDealPerformer,
selectedStage, selectedStage,
utmFile,
questionEntity, questionEntity,
tags, tags,
}) => { }) => {
@ -68,17 +65,6 @@ export const SettingItem: FC<SettingItemProps> = ({
); );
} }
if (step === 4) { if (step === 4) {
return (
<Box sx={{ display: "flex", gap: "15px", marginTop: "20px" }}>
{utmFile ? (
<FileBlock file={utmFile} />
) : (
<Typography>Файл не загружен</Typography>
)}
</Box>
);
}
if (step === 5) {
const isFilled = Object.values(questionEntity).some( const isFilled = Object.values(questionEntity).some(
(array) => array.length > 0, (array) => array.length > 0,
); );
@ -110,7 +96,7 @@ export const SettingItem: FC<SettingItemProps> = ({
</> </>
); );
} }
if (step === 6) { if (step === 5) {
const isFilled = Object.values(tags).some((array) => array.length > 0); const isFilled = Object.values(tags).some((array) => array.length > 0);
const status = isFilled ? "Заполнено" : "Не заполнено"; const status = isFilled ? "Заполнено" : "Не заполнено";
@ -148,7 +134,6 @@ export const SettingItem: FC<SettingItemProps> = ({
selectedStagePerformer, selectedStagePerformer,
selectedDealPerformer, selectedDealPerformer,
selectedStage, selectedStage,
utmFile,
questionEntity, questionEntity,
tags, tags,
]); ]);

@ -5,7 +5,6 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
import { useQuizStore } from "@root/quizes/store"; import { useQuizStore } from "@root/quizes/store";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { PartnersBoard } from "./PartnersBoard/PartnersBoard"; import { PartnersBoard } from "./PartnersBoard/PartnersBoard";
import { partnersMock } from "./mocks/MockData";
import { QuizMetricType } from "@model/quizSettings"; import { QuizMetricType } from "@model/quizSettings";
interface IntegrationsPageProps { interface IntegrationsPageProps {
@ -26,6 +25,9 @@ export const IntegrationsPage = ({
const [companyName, setCompanyName] = useState< const [companyName, setCompanyName] = useState<
keyof typeof QuizMetricType | null keyof typeof QuizMetricType | null
>(null); >(null);
const [isAmoCrmModalOpen, setIsAmoCrmModalOpen] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
if (editQuizId === null) navigate("/list"); if (editQuizId === null) navigate("/list");
}, [navigate, editQuizId]); }, [navigate, editQuizId]);
@ -38,9 +40,9 @@ export const IntegrationsPage = ({
const handleCloseModal = () => { const handleCloseModal = () => {
setIsModalOpen(false); setIsModalOpen(false);
// setTimeout(() => { };
// setCompanyName(null); const handleCloseAmoSRMModal = () => {
// }, 300); setIsAmoCrmModalOpen(false);
}; };
return ( return (
@ -63,18 +65,15 @@ export const IntegrationsPage = ({
Интеграции Интеграции
</Typography> </Typography>
<PartnersBoard <PartnersBoard
partners={partnersMock}
setIsModalOpen={setIsModalOpen} setIsModalOpen={setIsModalOpen}
companyName={companyName} companyName={companyName}
setCompanyName={setCompanyName} setCompanyName={setCompanyName}
isModalOpen={isModalOpen} isModalOpen={isModalOpen}
handleCloseModal={handleCloseModal} handleCloseModal={handleCloseModal}
setIsAmoCrmModalOpen={setIsAmoCrmModalOpen}
isAmoCrmModalOpen={isAmoCrmModalOpen}
handleCloseAmoSRMModal={handleCloseAmoSRMModal}
/> />
{/*<IntegrationsModal*/}
{/* isModalOpen={isModalOpen}*/}
{/* handleCloseModal={handleCloseModal}*/}
{/* companyName={companyName}*/}
{/*/>*/}
</Box> </Box>
</> </>
); );

@ -25,11 +25,11 @@ interface Props {
companyName: keyof typeof QuizMetricType; companyName: keyof typeof QuizMetricType;
} }
export default function AnalyticsModal({ export const AnalyticsModal = ({
isModalOpen, isModalOpen,
handleCloseModal, handleCloseModal,
companyName, companyName,
}: Props) { }: Props) => {
const theme = useTheme(); const theme = useTheme();
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
@ -260,4 +260,4 @@ export default function AnalyticsModal({
</Box> </Box>
</Dialog> </Dialog>
); );
} };

@ -1,50 +0,0 @@
import { Box, Typography, useTheme } from "@mui/material";
import { FC } from "react";
import { Partner } from "../PartnersBoard";
type PartnerItemProps = {
partner: Partner;
setIsModalOpen: (value: boolean) => void;
setCompanyName: (value: string) => void;
};
export const PartnerItem: FC<PartnerItemProps> = ({
partner,
setIsModalOpen,
setCompanyName,
}) => {
const theme = useTheme();
const handleClick = () => {
setCompanyName(partner.name);
setIsModalOpen(true);
};
return (
<>
{partner && (
<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}
>
{partner.logo ? (
<img height={"100%"} src={partner.logo} alt={partner.name} />
) : (
<Typography>{partner.name}</Typography>
)}
</Box>
)}
</>
);
};

@ -1,43 +1,47 @@
import { Box, Typography, useTheme } from "@mui/material"; import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import { FC } from "react"; import React, { FC, lazy, Suspense } from "react";
import { ServiceButton } from "./ServiceButton/ServiceButton"; import { ServiceButton } from "./ServiceButton/ServiceButton";
import { YandexMetricaLogo } from "../mocks/YandexMetricaLogo"; import { YandexMetricaLogo } from "../mocks/YandexMetricaLogo";
import AnalyticsModal from "./AnalyticsModal/AnalyticsModal"; // 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";
export type Partner = { const AnalyticsModal = lazy(() =>
name: string; import("./AnalyticsModal/AnalyticsModal").then((module) => ({
logo?: string; default: module.AnalyticsModal,
category: string; })),
}; );
const AmoCRMModal = lazy(() =>
import("../IntegrationsModal/AmoCRMModal").then((module) => ({
default: module.AmoCRMModal,
})),
);
type PartnersBoardProps = { type PartnersBoardProps = {
partners: Partner[];
setIsModalOpen: (value: boolean) => void; setIsModalOpen: (value: boolean) => void;
companyName: keyof typeof QuizMetricType | null; companyName: keyof typeof QuizMetricType | null;
setCompanyName: (value: keyof typeof QuizMetricType) => void; setCompanyName: (value: keyof typeof QuizMetricType) => void;
isModalOpen: boolean; isModalOpen: boolean;
handleCloseModal: () => void; handleCloseModal: () => void;
setIsAmoCrmModalOpen: (value: boolean) => void;
isAmoCrmModalOpen: boolean;
handleCloseAmoSRMModal: () => void;
}; };
export const PartnersBoard: FC<PartnersBoardProps> = ({ export const PartnersBoard: FC<PartnersBoardProps> = ({
partners,
setIsModalOpen, setIsModalOpen,
isModalOpen, isModalOpen,
handleCloseModal, handleCloseModal,
companyName, companyName,
setCompanyName, setCompanyName,
setIsAmoCrmModalOpen,
isAmoCrmModalOpen,
handleCloseAmoSRMModal,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600));
// const partnersByCategory = partners.reduce(
// (acc, partner) => {
// (acc[partner.category] = acc[partner.category] || []).push(partner);
// return acc;
// },
// {} as Record<string, Partner[]>,
// );
return ( return (
<Box <Box
@ -48,39 +52,32 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
justifyContent: { xs: "center", sm: "center", md: "start" }, justifyContent: { xs: "center", sm: "center", md: "start" },
}} }}
> >
{/*{Object.entries(partnersByCategory).map(([category, partners]) => (*/}
{/* <Box key={category}>*/}
{/* <Typography*/}
{/* variant="h6"*/}
{/* sx={{*/}
{/* textAlign: { xs: "center", sm: "start", md: "start" },*/}
{/* lineHeight: "1",*/}
{/* marginBottom: "12px",*/}
{/* }}*/}
{/* >*/}
{/* {category}*/}
{/* </Typography>*/}
{/* <Box*/}
{/* sx={{*/}
{/* display: "flex",*/}
{/* flexWrap: "wrap",*/}
{/* justifyContent: { xs: "center", sm: "start", md: "start" },*/}
{/* }}*/}
{/* >*/}
{/* {partners.map((partner) => (*/}
{/* <PartnerItem*/}
{/* key={partner.name}*/}
{/* partner={partner}*/}
{/* setIsModalOpen={setIsModalOpen}*/}
{/* setCompanyName={setCompanyName}*/}
{/* />*/}
{/* ))}*/}
{/* </Box>*/}
{/* </Box>*/}
{/*))}*/}
<Box> <Box>
<Typography
variant="h6"
sx={{
textAlign: { xs: "start", sm: "start", md: "start" },
lineHeight: "1",
marginBottom: "12px",
}}
>
CRM
</Typography>
<Box
sx={{
display: "flex",
flexWrap: "wrap",
justifyContent: { xs: "start", sm: "start", md: "start" },
}}
>
<ServiceButton
logo={<AmoCRMLogo />}
setIsModalOpen={setIsAmoCrmModalOpen}
setCompanyName={setCompanyName}
name={"amoCRM"}
/>
</Box>
<Typography <Typography
variant="h6" variant="h6"
sx={{ sx={{
@ -114,11 +111,22 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
</Box> </Box>
</Box> </Box>
{companyName && ( {companyName && (
<AnalyticsModal <Suspense>
isModalOpen={isModalOpen} <AnalyticsModal
handleCloseModal={handleCloseModal} isModalOpen={isModalOpen}
companyName={companyName} handleCloseModal={handleCloseModal}
/> companyName={companyName}
/>
</Suspense>
)}
{companyName && (
<Suspense>
<AmoCRMModal
isModalOpen={isAmoCrmModalOpen}
handleCloseModal={handleCloseAmoSRMModal}
companyName={companyName}
/>
</Suspense>
)} )}
</Box> </Box>
); );

@ -0,0 +1,6 @@
import React from "react";
import { ReactComponent as AmoLogo } from "./amoCRMLogo.svg";
export const AmoCRMLogo = () => {
return <AmoLogo />;
};

@ -1,9 +1,3 @@
import amoCrmLogo from "./amoCrmLogo.png";
export const partnersMock = [
{ category: "CRM", name: "amoCRM", logo: amoCrmLogo },
];
export const performersMock = [ export const performersMock = [
"Ангелина Полякова", "Ангелина Полякова",
"Петр Иванов", "Петр Иванов",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

@ -130,7 +130,7 @@ export default function OptionsAndPicture({
if (selectedVariantId) if (selectedVariantId)
clearQuestionImages(question.id, selectedVariantId); clearQuestionImages(question.id, selectedVariantId);
}} }}
cropAspectRatio={{ width: 452, height: 300 }} cropAspectRatio={{ width: 300, height: 300 }}
/> />
<Box <Box
sx={{ sx={{

@ -10,6 +10,7 @@ import { QuizStartpageType } from "@model/quizSettings";
import InfoIcon from "@icons/InfoIcon"; import InfoIcon from "@icons/InfoIcon";
import UploadBox from "@ui_kit/UploadBox"; import UploadBox from "@ui_kit/UploadBox";
import UploadIcon from "../../assets/icons/UploadIcon"; import UploadIcon from "../../assets/icons/UploadIcon";
import CustomizedSwitch from "@ui_kit/CustomSwitch";
import { import {
Box, Box,
@ -46,6 +47,7 @@ import { DropZone } from "./dropZone";
import Extra from "./extra"; import Extra from "./extra";
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo"; import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
import { VideoElement } from "./VideoElement"; import { VideoElement } from "./VideoElement";
import * as React from "react";
const designTypes = [ const designTypes = [
[ [
@ -868,6 +870,22 @@ export default function StartPageSettings() {
maxLength={1000} maxLength={1000}
/> />
<Extra /> <Extra />
<Box sx={{display: "flex", gap: "20px", alignItems: "center"}}>
<CustomizedSwitch
checked={quiz.config.antifraud}
onChange={(e) => {
updateQuiz(quiz.id, (quiz) => {
quiz.config.antifraud = e.target.checked;
})
}}
/>
<Typography sx={{fontWeight: 500,
color: theme.palette.grey3.main,}}
>
Включить антифрод</Typography>
</Box>
</> </>
)} )}
</Box> </Box>

@ -62,6 +62,8 @@ export interface Quizes {
law?: string; law?: string;
}; };
meta: string; meta: string;
antifraud: boolean;
showfc: boolean;
}; };
} }
@ -150,6 +152,8 @@ export const quizStore = create<QuizStore>()(
law: "", law: "",
}, },
meta: "что-то", meta: "что-то",
antifraud: true,
showfc: true
}, },
}; };
set({ listQuizes: newListQuizes }); set({ listQuizes: newListQuizes });