frontPanel/src/pages/PersonalizationAI/PersonalizationAI.tsx
Nastya 41340d7b6d
Some checks failed
Deploy / CreateImage (push) Failing after 52s
Deploy / DeployService (push) Has been skipped
текст в АИ раздельный
2025-06-23 08:19:59 +03:00

407 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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}
/>
</>
);
}