2025-06-01 21:27:22 +00:00
import { Box , Container , Typography , TextField , Button , List , ListItem , IconButton , Modal } from "@mui/material" ;
2025-05-25 13:21:19 +00:00
import { InfoPopover } from '@ui_kit/InfoPopover' ;
import GenderAndAgeSelector from "./GenderAndAgeSelector" ;
2025-06-01 14:05:41 +00:00
import { useEffect , useState } from "react" ;
2025-05-25 13:21:19 +00:00
import CustomTextField from "@ui_kit/CustomTextField" ;
import { useTheme } from "@mui/material" ;
2025-06-01 21:27:22 +00:00
import { AuditoryItem , auditoryAdd , auditoryDelete , auditoryGet } from "@/api/auditory" ;
2025-06-01 14:05:41 +00:00
import { useCurrentQuiz } from "@/stores/quizes/hooks" ;
import { AuditoryList } from "./AuditoryList" ;
2025-06-01 21:36:55 +00:00
import { useSnackbar } from "notistack" ;
2025-06-08 17:52:55 +00:00
import { PayModal } from "./PayModal" ;
import { useUserStore } from "@/stores/user" ;
import { cartApi } from "@/api/cart" ;
2025-06-08 20:23:24 +00:00
import { outCart } from "../Tariffs/Tariffs" ;
import { inCart } from "../Tariffs/Tariffs" ;
import { isTestServer } from "@/utils/hooks/useDomainDefine" ;
import { useToken } from "@frontend/kitui" ;
2025-06-09 13:16:55 +00:00
import { useSWRConfig } from "swr" ;
import { makeRequest } from "@api/makeRequest" ;
import { setUserAccount , setCustomerAccount } from "@/stores/user" ;
2025-06-10 20:22:18 +00:00
import { quizApi } from "@api/quiz" ;
import { setQuizes } from "@root/quizes/actions" ;
2025-06-16 05:24:10 +00:00
import TooltipClickInfo from "@/ui_kit/Toolbars/TooltipClickInfo" ;
2025-05-25 13:21:19 +00:00
2025-06-17 21:23:50 +00:00
const tariff = isTestServer ? "6844b8858258f5cc35791ef7" : "6851db40acfb4d3e5fcd9b19" ;
2025-05-25 13:21:19 +00:00
export default function PersonalizationAI() {
const theme = useTheme ( ) ;
2025-06-01 21:27:22 +00:00
const [ auditory , setAuditory ] = useState < AuditoryItem [ ] > ( [ ] ) ;
const [ deleteModal , setDeleteModal ] = useState < number > ( 0 ) ;
2025-06-01 22:48:35 +00:00
const [ link , setLink ] = useState < string > ( '' ) ;
const [ utmParams , setUtmParams ] = useState < string > ( '' ) ;
2025-06-01 21:27:22 +00:00
const quiz = useCurrentQuiz ( ) ;
2025-06-01 21:36:55 +00:00
const { enqueueSnackbar } = useSnackbar ( ) ;
2025-06-08 17:52:55 +00:00
const privilegesOfUser = useUserStore ( ( state ) = > state . userAccount ? . privileges ) ;
2025-06-08 20:23:24 +00:00
const user = useUserStore ( ( state ) = > state . customerAccount ) ;
const token = useToken ( ) ;
const userId = useUserStore ( ( state ) = > state . userId ) ;
2025-06-08 17:52:55 +00:00
const [ gender , setGender ] = useState < string > ( '' ) ;
const [ age , setAge ] = useState < string > ( '' ) ;
const [ ageError , setAgeError ] = useState ( false ) ;
const [ isModalOpen , setIsModalOpen ] = useState ( false ) ;
2025-06-09 13:16:55 +00:00
// Обновляем данные пользователя через SWR
const { mutate } = useSWRConfig ( ) ;
2025-06-08 17:52:55 +00:00
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 : {
2025-06-08 19:56:22 +00:00
sex : parseInt ( gender ) ,
2025-06-08 17:52:55 +00:00
age
}
} ) ;
if ( error ) {
enqueueSnackbar ( 'Н е удалось добавить ссылку' , { variant : 'error' } ) ;
2025-06-09 13:16:55 +00:00
return [ , error ] ;
2025-06-08 17:52:55 +00:00
}
if ( result ) {
handleAdd ( {
id : result.ID ,
quiz_id : quiz.backendId ,
2025-06-08 19:56:22 +00:00
sex : parseInt ( gender ) ,
2025-06-08 17:52:55 +00:00
age ,
deleted : false ,
2025-06-08 19:56:22 +00:00
created_at : Date.now ( )
2025-06-08 17:52:55 +00:00
} ) ;
enqueueSnackbar ( 'Ссылка успешно добавлена' , { variant : 'success' } ) ;
resetForm ( ) ;
setIsModalOpen ( false ) ;
2025-06-09 13:16:55 +00:00
// Обновляем данные пользователя после успешного создания ссылки
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" } ) ;
}
2025-06-08 17:52:55 +00:00
}
} catch ( error ) {
enqueueSnackbar ( 'Произошла ошибка при добавлении' , { variant : 'error' } ) ;
}
} ;
2025-06-01 21:27:22 +00:00
useEffect ( ( ) = > {
( async ( ) = > {
if ( quiz ? . backendId ) {
const [ result , error ] = await auditoryGet ( { quizId : quiz.backendId } ) ;
console . log ( "result-___---_------__---__-__---_------__---__-__---_------__---__-__---_------__---__-____--__" )
console . log ( result )
if ( result ) {
setAuditory ( result ) ;
}
}
} ) ( ) ;
} , [ quiz ] ) ;
2025-06-01 21:36:55 +00:00
const handleDelete = async ( ) = > {
// 1. Закрываем модалку
setDeleteModal ( 0 ) ;
2025-06-06 13:25:06 +00:00
2025-06-01 21:36:55 +00:00
// 2. Находим индекс объекта в стейте
const indexToDelete = auditory . findIndex ( item = > item . id === deleteModal ) ;
if ( indexToDelete === - 1 ) return ;
2025-06-06 13:25:06 +00:00
2025-06-01 21:36:55 +00:00
// 3. Сохраняем удаляемый объект
const deletedItem = auditory [ indexToDelete ] ;
2025-06-06 13:25:06 +00:00
2025-06-01 21:36:55 +00:00
// 4. Меняем стейт, вырезая объект
setAuditory ( prev = > prev . filter ( item = > item . id !== deleteModal ) ) ;
2025-06-06 13:25:06 +00:00
2025-06-01 21:36:55 +00:00
try {
// 5. Вызываем функцию удаления
2025-06-06 13:25:06 +00:00
const [ result , error ] = await auditoryDelete ( {
quizId : quiz?.backendId ,
auditoryId : deleteModal
2025-06-01 21:36:55 +00:00
} ) ;
2025-06-06 13:25:06 +00:00
2025-06-01 21:36:55 +00:00
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 ;
} ) ;
}
2025-06-01 21:27:22 +00:00
}
2025-05-25 13:21:19 +00:00
2025-06-06 13:25:06 +00:00
const handleAdd = ( item : AuditoryItem ) = > {
2025-06-08 17:52:55 +00:00
setAuditory ( old = > ( [ . . . old , item ] ) ) ;
// Очищаем форму после успешного добавления
setGender ( '' ) ;
setAge ( '' ) ;
2025-06-01 22:02:43 +00:00
}
2025-06-01 22:48:35 +00:00
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 ( '&' ) ;
2025-06-02 15:53:46 +00:00
setUtmParams ( paramString ? ` & ${ paramString } ` : "" ) ;
2025-06-01 22:48:35 +00:00
} ;
2025-06-10 20:22:18 +00:00
console . log ( "______----giga_chat-----__--_---_--_----__--__-__--_--__--__--_---_______-quiz" )
console . log ( quiz ? . giga_chat )
2025-06-08 20:08:47 +00:00
const startCreate = async ( ) = > {
2025-06-10 20:22:18 +00:00
if ( quiz ? . giga_chat ) {
2025-06-08 20:08:47 +00:00
createNewLink ( ) ;
} else {
2025-06-08 17:52:55 +00:00
setIsModalOpen ( true ) ;
}
} ;
2025-06-08 20:23:24 +00:00
const tryBuy = async ( { id , price } : { id : string ; price : number } ) = > {
//Если в корзине что-то было - выкладываем содержимое и запоминаем чо там лежало
2025-06-09 13:16:55 +00:00
if ( user ? . cart ? . length > 0 ) {
2025-06-08 20:23:24 +00:00
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" ) ;
2025-06-09 14:26:19 +00:00
link . href = ` https:// ${ isTestServer ? "s" : "" } hub.pena.digital/quizpayment?action=squizpay&dif=50000&data= ${ token } &userid= ${ userId } &from=AI&wayback=ai_ ${ quiz ? . backendId } ` ;
2025-06-08 20:23:24 +00:00
document . body . appendChild ( link ) ;
link . click ( ) ;
return ;
}
//другая ошибка
enqueueSnackbar ( "Произошла ошибка. Попробуйте позже" ) ;
return ;
}
//Развращаем товары в корзину
inCart ( ) ;
2025-06-09 13:16:55 +00:00
2025-06-10 20:22:18 +00:00
//Показываем сообщение о б успешной покупке
enqueueSnackbar ( "Тариф успешно приобретен" , { variant : "success" } ) ;
// Создаем новую ссылку после обновления данных
await createNewLink ( ) ;
2025-06-22 13:03:26 +00:00
2025-06-10 20:22:18 +00:00
// Обновляем данные квиза после успешной оплаты
console . log ( "Обновляем данные квиза после оплаты" ) ;
const [ quizes , quizesError ] = await quizApi . getList ( ) ;
console . log ( "Получены данные квизов:" , quizes ) ;
if ( ! quizesError ) {
setQuizes ( quizes ) ;
console . log ( "Данные квизов обновлены в сторе" ) ;
} else {
console . error ( "Ошибка при получении данных квизов:" , quizesError ) ;
}
2025-06-09 13:16:55 +00:00
2025-06-08 20:23:24 +00:00
} ;
2025-06-08 20:08:47 +00:00
2025-05-25 13:21:19 +00:00
return (
2025-06-01 21:27:22 +00:00
< >
2025-06-06 13:25:06 +00:00
< Container id = "PersonalizationAI" maxWidth = { false } sx = { { minHeight : "100%" , p : "20px" , height : " calc(100vh - 80px)" , overflow : "auto" , pt : "55px" } } >
2025-06-01 21:27:22 +00:00
< 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"
} } >
2025-06-22 13:03:26 +00:00
Д а н н ы й р а з д е л п о з в о л я е т в а м с о з д а в а т ь п е р с о н а л и з и р о в а н н ы й о п р о с п о д к а ж д у ю ц е л е в у ю а у д и т о р и ю о т д е л ь н о , н а ш 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 >
2025-06-01 21:27:22 +00:00
< Box sx = { {
bgcolor : "#fff" ,
borderRadius : "12px" ,
mt : "40px" ,
p : "20px 20px 30px" ,
boxShadow : "none" ,
maxWidth : "796px"
} } >
2025-06-08 20:08:47 +00:00
< GenderAndAgeSelector
2025-06-08 17:52:55 +00:00
gender = { gender }
age = { age }
ageError = { ageError }
onGenderChange = { setGender }
onAgeChange = { setAge }
onAgeErrorChange = { setAgeError }
onStartCreate = { startCreate }
/ >
2025-05-25 13:21:19 +00:00
2025-06-01 21:27:22 +00:00
{ /* Ссылка */ }
< Box sx = { { mt : "34px" } } >
< Box sx = { { display : 'flex' , alignItems : 'center' , gap : '6px' } } >
< Typography sx = { { color : theme.palette.grey3.main , fontSize : "18px" , fontWeight : 500 } } > С с ы л к а < / Typography >
2025-06-22 13:03:26 +00:00
< TooltipClickInfo title = { ` Данное поле создано для обогащения utm метками вашей ссылки. Нужно скопировать ссылку вашего квиза, задать настройки ца, вставить ссылку в поле, прописать метки(советуем использовать динамические), и нажать "ок" выше поля. Метки будут применены ко всем ссылкам с персонализацией в рамках данного квиза. ` } / >
2025-06-16 05:24:10 +00:00
{ / * < I n f o P o p o v e r >
< Typography sx = { { maxWidth : "300px" } } >
Д а н н о е п о л е с о з д а н о д л я о б о г а щ е н и я utm м е т к а м и в а ш е й с с ы л к и . Н у ж н о с к о п и р о в а т ь с с ы л к у в а ш е г о к в и з а , з а д а т ь н а с т р о й к и ц а , в с т а в и т ь с с ы л к у в п о л е , п р о п и с а т ь м е т к и ( с о в е т у е м и с п о л ь з о в а т ь д и н а м и ч е с к и е ) , и н а ж а т ь "ок" в ы ш е п о л я . М е т к и б у д у т п р и м е н е н ы к о в с е м с с ы л к а м с п е р с о н а л и з а ц и е й в р а м к а х д а н н о г о к в и з а .
< / Typography >
< / InfoPopover > * / }
2025-06-01 21:27:22 +00:00
< / Box >
< Typography
sx = { {
fontSize : "14px" ,
lineHeight : "100%" ,
letterSpacing : "0 %" ,
color : theme.palette.grey2.main ,
mt : "16px"
} }
>
В с т а в ь т е с с ы л к у с о в с е м и utm - м е т к а м и
< / Typography >
2025-05-25 13:21:19 +00:00
< CustomTextField
placeholder = "linkexample.com"
2025-06-01 22:48:35 +00:00
maxLength = { 500 }
value = { link }
onChange = { handleLinkChange }
2025-05-25 13:21:19 +00:00
sxForm = { {
2025-06-02 18:22:55 +00:00
maxWidth : "615px" ,
width : "100%" ,
2025-05-25 13:21:19 +00:00
} }
/ >
< / Box >
< / Box >
2025-06-01 22:48:35 +00:00
< AuditoryList utmParams = { utmParams } onDelete = { setDeleteModal } auditory = { auditory } / >
2025-05-25 13:21:19 +00:00
2025-06-01 21:27:22 +00:00
< / 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"
} }
>
2025-06-06 13:25:06 +00:00
< Typography sx = { { width : "100%" , textAlign : "center" , mb : "25px" } } > У в е р е н ы , ч т о х о т и т е у д а л и т ь с с ы л к у ? < / Typography >
2025-06-17 21:23:50 +00:00
< Button sx = { { mb : "20px" } } id = "delete_OK" onClick = { handleDelete } > У д а л и т ь < / Button >
2025-06-01 21:27:22 +00:00
< Button variant = "contained" onClick = { ( ) = > setDeleteModal ( 0 ) } > О т м е н а < / Button >
< / Box >
< / Modal >
2025-06-08 17:52:55 +00:00
< PayModal
open = { isModalOpen }
onClose = { ( ) = > {
setIsModalOpen ( false ) ;
} }
2025-06-08 20:08:47 +00:00
onCreate = { tryBuy }
2025-06-08 17:52:55 +00:00
/ >
2025-06-01 21:27:22 +00:00
< / >
2025-05-25 13:21:19 +00:00
) ;
}