407 lines
16 KiB
TypeScript
407 lines
16 KiB
TypeScript
import { Box, Container, Typography, TextField, Button, List, ListItem, IconButton, Modal } from "@mui/material";
|
||
import { InfoPopover } from '@ui_kit/InfoPopover';
|
||
import GenderAndAgeSelector from "./GenderAndAgeSelector";
|
||
import { useEffect, useState } from "react";
|
||
import CustomTextField from "@ui_kit/CustomTextField";
|
||
import { useTheme } from "@mui/material";
|
||
import { AuditoryItem, auditoryAdd, auditoryDelete, auditoryGet } from "@/api/auditory";
|
||
import { useCurrentQuiz } from "@/stores/quizes/hooks";
|
||
import { AuditoryList } from "./AuditoryList";
|
||
import { useSnackbar } from "notistack";
|
||
import { PayModal } from "./PayModal";
|
||
import { useUserStore } from "@/stores/user";
|
||
import { cartApi } from "@/api/cart";
|
||
import { outCart } from "../Tariffs/Tariffs";
|
||
import { inCart } from "../Tariffs/Tariffs";
|
||
import { isTestServer } from "@/utils/hooks/useDomainDefine";
|
||
import { useToken } from "@frontend/kitui";
|
||
import { useSWRConfig } from "swr";
|
||
import { makeRequest } from "@api/makeRequest";
|
||
import { setUserAccount, setCustomerAccount } from "@/stores/user";
|
||
import { quizApi } from "@api/quiz";
|
||
import { setQuizes } from "@root/quizes/actions";
|
||
import TooltipClickInfo from "@/ui_kit/Toolbars/TooltipClickInfo";
|
||
|
||
const tariff = isTestServer ? "6844b8858258f5cc35791ef7" : "6851db40acfb4d3e5fcd9b19";
|
||
export default function PersonalizationAI() {
|
||
const theme = useTheme();
|
||
const [auditory, setAuditory] = useState<AuditoryItem[]>([]);
|
||
const [deleteModal, setDeleteModal] = useState<number>(0);
|
||
const [link, setLink] = useState<string>('');
|
||
const [utmParams, setUtmParams] = useState<string>('');
|
||
const quiz = useCurrentQuiz();
|
||
const { enqueueSnackbar } = useSnackbar();
|
||
const privilegesOfUser = useUserStore((state) => state.userAccount?.privileges);
|
||
const user = useUserStore((state) => state.customerAccount);
|
||
const token = useToken();
|
||
const userId = useUserStore((state) => state.userId);
|
||
|
||
const [gender, setGender] = useState<string>('');
|
||
const [age, setAge] = useState<string>('');
|
||
const [ageError, setAgeError] = useState(false);
|
||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||
// Обновляем данные пользователя через SWR
|
||
const { mutate } = useSWRConfig();
|
||
|
||
const resetForm = () => {
|
||
setGender('');
|
||
setAge('');
|
||
setAgeError(false);
|
||
};
|
||
|
||
|
||
const createNewLink = async () => {
|
||
if (!quiz?.backendId) {
|
||
enqueueSnackbar('Ошибка: не выбран квиз', { variant: 'error' });
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const [result, error] = await auditoryAdd({
|
||
quizId: quiz.backendId,
|
||
body: {
|
||
sex: parseInt(gender),
|
||
age
|
||
}
|
||
});
|
||
|
||
if (error) {
|
||
enqueueSnackbar('Не удалось добавить ссылку', { variant: 'error' });
|
||
return [, error];
|
||
}
|
||
|
||
if (result) {
|
||
handleAdd({
|
||
id: result.ID,
|
||
quiz_id: quiz.backendId,
|
||
sex: parseInt(gender),
|
||
age,
|
||
deleted: false,
|
||
created_at: Date.now()
|
||
});
|
||
enqueueSnackbar('Ссылка успешно добавлена', { variant: 'success' });
|
||
resetForm();
|
||
setIsModalOpen(false);
|
||
|
||
// Обновляем данные пользователя после успешного создания ссылки
|
||
try {
|
||
const [userAccountResult, customerAccountResult] = await Promise.all([
|
||
makeRequest({
|
||
url: `${process.env.REACT_APP_DOMAIN}/squiz/account/get`,
|
||
method: "GET",
|
||
useToken: true,
|
||
withCredentials: false,
|
||
}).catch(error => {
|
||
console.log(error)
|
||
enqueueSnackbar("Ошибка при обновлении данных пользователя", { variant: "error" });
|
||
return null;
|
||
}),
|
||
makeRequest({
|
||
url: `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1/account`,
|
||
method: "GET",
|
||
useToken: true,
|
||
withCredentials: false,
|
||
}).catch(error => {
|
||
console.log(error)
|
||
enqueueSnackbar("Ошибка при обновлении данных клиента", { variant: "error" });
|
||
return null;
|
||
})
|
||
]);
|
||
|
||
if (userAccountResult) {
|
||
setUserAccount(userAccountResult);
|
||
}
|
||
if (customerAccountResult) {
|
||
setCustomerAccount(customerAccountResult);
|
||
}
|
||
} catch (error) {
|
||
console.log(error)
|
||
enqueueSnackbar("Ошибка при обновлении данных", { variant: "error" });
|
||
}
|
||
}
|
||
} catch (error) {
|
||
enqueueSnackbar('Произошла ошибка при добавлении', { variant: 'error' });
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
(async () => {
|
||
if (quiz?.backendId) {
|
||
const [result, error] = await auditoryGet({ quizId: quiz.backendId });
|
||
console.log("result-___---_------__---__-__---_------__---__-__---_------__---__-__---_------__---__-____--__")
|
||
console.log(result)
|
||
if (result) {
|
||
setAuditory(result);
|
||
}
|
||
}
|
||
})();
|
||
}, [quiz]);
|
||
|
||
const handleDelete = async () => {
|
||
// 1. Закрываем модалку
|
||
setDeleteModal(0);
|
||
|
||
// 2. Находим индекс объекта в стейте
|
||
const indexToDelete = auditory.findIndex(item => item.id === deleteModal);
|
||
if (indexToDelete === -1) return;
|
||
|
||
// 3. Сохраняем удаляемый объект
|
||
const deletedItem = auditory[indexToDelete];
|
||
|
||
// 4. Меняем стейт, вырезая объект
|
||
setAuditory(prev => prev.filter(item => item.id !== deleteModal));
|
||
|
||
try {
|
||
// 5. Вызываем функцию удаления
|
||
const [result, error] = await auditoryDelete({
|
||
quizId: quiz?.backendId,
|
||
auditoryId: deleteModal
|
||
});
|
||
|
||
if (error) {
|
||
// 6. Если удалить не удалось - показываем снекбар и возвращаем ссылку
|
||
enqueueSnackbar('Не удалось удалить ссылку', { variant: 'error' });
|
||
setAuditory(prev => {
|
||
const newArray = [...prev];
|
||
newArray.splice(indexToDelete, 0, deletedItem);
|
||
return newArray;
|
||
});
|
||
}
|
||
} catch (error) {
|
||
// Обработка ошибки сети или других ошибок
|
||
enqueueSnackbar('Произошла ошибка при удалении', { variant: 'error' });
|
||
setAuditory(prev => {
|
||
const newArray = [...prev];
|
||
newArray.splice(indexToDelete, 0, deletedItem);
|
||
return newArray;
|
||
});
|
||
}
|
||
}
|
||
|
||
const handleAdd = (item: AuditoryItem) => {
|
||
setAuditory(old => ([...old, item]));
|
||
// Очищаем форму после успешного добавления
|
||
setGender('');
|
||
setAge('');
|
||
}
|
||
|
||
const handleLinkChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const newLink = e.target.value;
|
||
setLink(newLink);
|
||
|
||
// Регулярное выражение для поиска параметров URL
|
||
const paramRegex = /[?&]([^=&]+)=([^&]*)/g;
|
||
const params: Record<string, string> = {};
|
||
let match;
|
||
|
||
// Ищем все параметры в строке
|
||
while ((match = paramRegex.exec(newLink)) !== null) {
|
||
const key = decodeURIComponent(match[1]);
|
||
const value = decodeURIComponent(match[2]);
|
||
params[key] = value;
|
||
}
|
||
|
||
// Преобразуем объект параметров в строку URL
|
||
const paramString = Object.entries(params)
|
||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||
.join('&');
|
||
|
||
setUtmParams(paramString ? `&${paramString}` : "");
|
||
};
|
||
|
||
console.log("______----giga_chat-----__--_---_--_----__--__-__--_--__--__--_---_______-quiz")
|
||
console.log(quiz?.giga_chat)
|
||
const startCreate = async () => {
|
||
if (quiz?.giga_chat) {
|
||
createNewLink();
|
||
} else {
|
||
setIsModalOpen(true);
|
||
}
|
||
};
|
||
|
||
const tryBuy = async ({ id, price }: { id: string; price: number }) => {
|
||
//Если в корзине что-то было - выкладываем содержимое и запоминаем чо там лежало
|
||
if (user?.cart?.length > 0) {
|
||
outCart(user.cart);
|
||
}
|
||
//Добавляем желаемый тариф в корзину
|
||
const [_, addError] = await cartApi.add(tariff);
|
||
|
||
if (addError) {
|
||
//Развращаем товары в корзину
|
||
inCart();
|
||
return;
|
||
}
|
||
|
||
//Если нам хватает денежек - покупаем тариф
|
||
const [data, payError] = await cartApi.pay();
|
||
|
||
if (payError || !data) {
|
||
//если денег не хватило
|
||
if (payError?.includes("insufficient funds") || payError?.includes("Payment Required")) {
|
||
var link = document.createElement("a");
|
||
link.href = `https://${isTestServer ? "s" : ""}hub.pena.digital/quizpayment?action=squizpay&dif=50000&data=${token}&userid=${userId}&from=AI&wayback=ai_${quiz?.backendId}`;
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
return;
|
||
}
|
||
|
||
//другая ошибка
|
||
enqueueSnackbar("Произошла ошибка. Попробуйте позже");
|
||
return;
|
||
}
|
||
|
||
//Развращаем товары в корзину
|
||
inCart();
|
||
|
||
//Показываем сообщение об успешной покупке
|
||
enqueueSnackbar("Тариф успешно приобретен", { variant: "success" });
|
||
|
||
|
||
// Создаем новую ссылку после обновления данных
|
||
await createNewLink();
|
||
|
||
|
||
// Обновляем данные квиза после успешной оплаты
|
||
console.log("Обновляем данные квиза после оплаты");
|
||
const [quizes, quizesError] = await quizApi.getList();
|
||
console.log("Получены данные квизов:", quizes);
|
||
if (!quizesError) {
|
||
setQuizes(quizes);
|
||
console.log("Данные квизов обновлены в сторе");
|
||
} else {
|
||
console.error("Ошибка при получении данных квизов:", quizesError);
|
||
}
|
||
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<Container id="PersonalizationAI" maxWidth={false} sx={{ minHeight: "100%", p: "20px", height: " calc(100vh - 80px)", overflow: "auto", pt: "55px" }}>
|
||
<Typography variant="h5" color={theme.palette.grey3.main} fontWeight={700} sx={{ fontSize: 24, letterSpacing: "-0.2px" }}>
|
||
Персонализация вопросов с помощью AI
|
||
</Typography>
|
||
<Typography sx={{
|
||
color: theme.palette.grey3.main, fontSize: "18px", maxWidth: 796, m: 0,
|
||
mt: "19px",
|
||
letterSpacing: "0.009px",
|
||
wordSpacing: "0.1px",
|
||
lineHeight: "21.4px"
|
||
}}>
|
||
Данный раздел позволяет вам создавать персонализированный опрос под каждую целевую аудиторию отдельно, наш AI перефразирует ваши вопросы согласно настройкам.
|
||
Для этого нужно выбрать пол и возраст вашей аудитории и получите персональную ссылку с нужными настройками в списке ниже.
|
||
</Typography>
|
||
<Typography sx={{
|
||
color: theme.palette.grey3.main, fontSize: "18px", maxWidth: 796, m: 0,
|
||
mt: "19px",
|
||
letterSpacing: "0.009px",
|
||
wordSpacing: "0.1px",
|
||
lineHeight: "21.4px"
|
||
}}>
|
||
Так же вы можете обогатить свою ссылку UTM метками в поле "вставьте свою ссылку" и эти метки применятся ко всем вашим ссылкам.
|
||
</Typography>
|
||
<Typography sx={{
|
||
color: theme.palette.grey3.main, fontSize: "18px", maxWidth: 796, m: 0,
|
||
mt: "19px",
|
||
letterSpacing: "0.009px",
|
||
wordSpacing: "0.1px",
|
||
lineHeight: "21.4px"
|
||
}}>
|
||
ВАЖНО: если ваши вопросы уже подходят целевой аудитории, то персонализированы они скорее всего не будут.
|
||
</Typography>
|
||
<Box sx={{
|
||
bgcolor: "#fff",
|
||
borderRadius: "12px",
|
||
mt: "40px",
|
||
p: "20px 20px 30px",
|
||
boxShadow: "none",
|
||
maxWidth: "796px"
|
||
}}>
|
||
<GenderAndAgeSelector
|
||
gender={gender}
|
||
age={age}
|
||
ageError={ageError}
|
||
onGenderChange={setGender}
|
||
onAgeChange={setAge}
|
||
onAgeErrorChange={setAgeError}
|
||
onStartCreate={startCreate}
|
||
/>
|
||
|
||
{/* Ссылка */}
|
||
<Box sx={{ mt: "34px" }}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||
<Typography sx={{ color: theme.palette.grey3.main, fontSize: "18px", fontWeight: 500 }}>Ссылка</Typography>
|
||
<TooltipClickInfo title={`Данное поле создано для обогащения utm метками вашей ссылки. Нужно скопировать ссылку вашего квиза, задать настройки ца, вставить ссылку в поле, прописать метки(советуем использовать динамические), и нажать "ок" выше поля. Метки будут применены ко всем ссылкам с персонализацией в рамках данного квиза.`} />
|
||
{/* <InfoPopover >
|
||
<Typography sx={{maxWidth: "300px"}} >
|
||
Данное поле создано для обогащения utm метками вашей ссылки. Нужно скопировать ссылку вашего квиза, задать настройки ца, вставить ссылку в поле, прописать метки(советуем использовать динамические), и нажать "ок" выше поля. Метки будут применены ко всем ссылкам с персонализацией в рамках данного квиза.
|
||
</Typography>
|
||
</InfoPopover> */}
|
||
</Box>
|
||
<Typography
|
||
sx={{
|
||
fontSize: "14px",
|
||
lineHeight: "100%",
|
||
letterSpacing: "0 %",
|
||
color: theme.palette.grey2.main,
|
||
mt: "16px"
|
||
}}
|
||
>
|
||
Вставьте ссылку со всеми utm-метками
|
||
</Typography>
|
||
<CustomTextField
|
||
placeholder="linkexample.com"
|
||
maxLength={500}
|
||
value={link}
|
||
onChange={handleLinkChange}
|
||
sxForm={{
|
||
maxWidth: "615px",
|
||
width: "100%",
|
||
}}
|
||
/>
|
||
</Box>
|
||
</Box>
|
||
|
||
<AuditoryList utmParams={utmParams} onDelete={setDeleteModal} auditory={auditory} />
|
||
|
||
</Container>
|
||
<Modal
|
||
open={Boolean(deleteModal)}
|
||
onClose={() => setDeleteModal(0)}
|
||
aria-labelledby="modal-modal-title"
|
||
aria-describedby="modal-modal-description"
|
||
>
|
||
<Box
|
||
sx={{
|
||
position: "absolute" as "absolute",
|
||
top: "50%",
|
||
left: "50%",
|
||
transform: "translate(-50%, -50%)",
|
||
maxWidth: "620px",
|
||
width: "100%",
|
||
bgcolor: "background.paper",
|
||
borderRadius: "12px",
|
||
|
||
boxShadow: 24,
|
||
p: "20px",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
alignItems: "center"
|
||
}}
|
||
>
|
||
<Typography sx={{ width: "100%", textAlign: "center", mb: "25px" }}>Уверены, что хотите удалить ссылку?</Typography>
|
||
<Button sx={{ mb: "20px" }} id="delete_OK" onClick={handleDelete}>Удалить</Button>
|
||
<Button variant="contained" onClick={() => setDeleteModal(0)} >Отмена</Button>
|
||
</Box>
|
||
</Modal>
|
||
<PayModal
|
||
open={isModalOpen}
|
||
onClose={() => {
|
||
setIsModalOpen(false);
|
||
}}
|
||
onCreate={tryBuy}
|
||
/>
|
||
</>
|
||
);
|
||
}
|