z p модалки с корректными запросами
This commit is contained in:
parent
862ed4f395
commit
7da86c5b2e
@ -4,14 +4,14 @@ import { parseAxiosError } from "@utils/parse-error";
|
||||
export type LeadTargetType = "mail" | "telegram" | "whatsapp" | "webhook";
|
||||
|
||||
export interface LeadTargetModel {
|
||||
ID: number;
|
||||
AccountID: string;
|
||||
Type: LeadTargetType;
|
||||
QuizID: number;
|
||||
Target: string;
|
||||
InviteLink?: string;
|
||||
Deleted?: boolean;
|
||||
CreatedAt?: string;
|
||||
id: number;
|
||||
accountID: string;
|
||||
type: LeadTargetType;
|
||||
quizID: number;
|
||||
target: string; // содержит подстроку "zapier" или "postback"
|
||||
inviteLink?: string;
|
||||
deleted?: boolean;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
|
||||
|
||||
@ -3,10 +3,11 @@ import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box, Button }
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { Quiz } from "@/model/quiz/quiz";
|
||||
import CustomTextField from "@/ui_kit/CustomTextField";
|
||||
import { createLeadTarget, getLeadTargetsByQuiz, deleteLeadTarget } from "@/api/leadtarget";
|
||||
import { createLeadTarget, getLeadTargetsByQuiz, deleteLeadTarget, updateLeadTarget } from "@/api/leadtarget";
|
||||
import { useFormik } from "formik";
|
||||
import InstructionYoutubeLink from "@/pages/IntegrationsPage/IntegrationsModal/InstructionYoutubeLink";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useLeadTargets } from "@/pages/IntegrationsPage/hooks/useLeadTargets";
|
||||
|
||||
type PostbackModalProps = {
|
||||
isModalOpen: boolean;
|
||||
@ -27,28 +28,26 @@ export const PostbackModal: FC<PostbackModalProps> = ({
|
||||
const [isSaving, setIsSaving] = useState<boolean>(false);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const deleteLeadTargetsByQuizType = async (quizId: number, type: "webhook") => {
|
||||
const [targets] = await getLeadTargetsByQuiz(quizId);
|
||||
if (!targets || targets.length === 0) {
|
||||
console.log("No targets found for deletion");
|
||||
return;
|
||||
}
|
||||
const toDelete = targets.filter(t => t.Type === type);
|
||||
console.log("Targets to delete:", toDelete);
|
||||
for (const t of toDelete) {
|
||||
console.log("Deleting target with ID:", t.ID);
|
||||
await deleteLeadTarget(t.ID);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: { token: string; domain: string }) => {
|
||||
const tokenValue = (values.token || "").trim();
|
||||
const target = (values.domain || "").trim();
|
||||
try {
|
||||
setIsSaving(true);
|
||||
// 1) Асинхронно получаем текущие цели
|
||||
const [items] = await getLeadTargetsByQuiz(quiz.backendId);
|
||||
const existing = (items ?? []).filter((t) => t.type === "webhook");
|
||||
console.log("Saving flow -> existing webhook targets:", existing);
|
||||
if (!tokenValue && !target) {
|
||||
await deleteLeadTargetsByQuizType(quiz.backendId, "webhook");
|
||||
const deletePromises = existing.map((t) => deleteLeadTarget(t.id));
|
||||
await Promise.all(deletePromises);
|
||||
enqueueSnackbar("Postback удален", { variant: "success" });
|
||||
} else if (existing.length > 0) {
|
||||
const [first, ...extra] = existing;
|
||||
await Promise.all([
|
||||
updateLeadTarget({ id: first.id, target }),
|
||||
...extra.map((t) => deleteLeadTarget(t.id)),
|
||||
]);
|
||||
enqueueSnackbar("Postback обновлен", { variant: "success" });
|
||||
} else {
|
||||
await createLeadTarget({
|
||||
type: "webhook",
|
||||
@ -58,6 +57,8 @@ export const PostbackModal: FC<PostbackModalProps> = ({
|
||||
});
|
||||
enqueueSnackbar("Postback сохранен", { variant: "success" });
|
||||
}
|
||||
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
enqueueSnackbar("Ошибка при сохранении", { variant: "error" });
|
||||
} finally {
|
||||
@ -65,11 +66,18 @@ export const PostbackModal: FC<PostbackModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const { isLoading, postbackTarget, refresh } = useLeadTargets(quiz?.backendId, isModalOpen);
|
||||
|
||||
const formik = useFormik<{ token: string; domain: string }>({
|
||||
initialValues: { token: "", domain: "" },
|
||||
initialValues: { token: "", domain: postbackTarget?.target ?? "" },
|
||||
onSubmit: handleSubmit,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
formik.setFieldValue("domain", postbackTarget?.target ?? "");
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [postbackTarget?.target]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isModalOpen) {
|
||||
setTimeout(() => {
|
||||
@ -153,6 +161,9 @@ export const PostbackModal: FC<PostbackModalProps> = ({
|
||||
>
|
||||
Домен
|
||||
</Typography>
|
||||
{isLoading ? (
|
||||
<Box sx={{ width: "100%", height: 44, borderRadius: "8px", bgcolor: "action.hover" }} />
|
||||
) : (
|
||||
<CustomTextField
|
||||
id="postback-domain"
|
||||
placeholder="токен в формате ХХХХХХ"
|
||||
@ -160,10 +171,11 @@ export const PostbackModal: FC<PostbackModalProps> = ({
|
||||
onChange={(e) => formik.setFieldValue("domain", e.target.value)}
|
||||
maxLength={150}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
disabled={isSaving}
|
||||
disabled={isSaving || isLoading}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
sx={{
|
||||
|
||||
@ -3,10 +3,11 @@ import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box, Button }
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { Quiz } from "@/model/quiz/quiz";
|
||||
import CustomTextField from "@/ui_kit/CustomTextField";
|
||||
import { createLeadTarget, getLeadTargetsByQuiz, deleteLeadTarget } from "@/api/leadtarget";
|
||||
import { createLeadTarget, getLeadTargetsByQuiz, deleteLeadTarget, updateLeadTarget } from "@/api/leadtarget";
|
||||
import { useFormik } from "formik";
|
||||
import InstructionYoutubeLink from "@/pages/IntegrationsPage/IntegrationsModal/InstructionYoutubeLink";
|
||||
import { useSnackbar } from "notistack";
|
||||
import { useLeadTargets } from "@/pages/IntegrationsPage/hooks/useLeadTargets";
|
||||
|
||||
type ZapierModalProps = {
|
||||
isModalOpen: boolean;
|
||||
@ -27,28 +28,30 @@ export const ZapierModal: FC<ZapierModalProps> = ({
|
||||
const [isSaving, setIsSaving] = useState<boolean>(false);
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const deleteLeadTargetsByQuizType = async (quizId: number, type: "webhook") => {
|
||||
const [targets] = await getLeadTargetsByQuiz(quizId);
|
||||
if (!targets || targets.length === 0) {
|
||||
console.log("No targets found for deletion");
|
||||
return;
|
||||
}
|
||||
const toDelete = targets.filter(t => t.Type === type);
|
||||
console.log("Targets to delete:", toDelete);
|
||||
for (const t of toDelete) {
|
||||
console.log("Deleting target with ID:", t.ID);
|
||||
await deleteLeadTarget(t.ID);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: { webhookUrl: string }) => {
|
||||
const target = (values.webhookUrl || "").trim();
|
||||
try {
|
||||
setIsSaving(true);
|
||||
// 1) Асинхронно получаем текущие цели
|
||||
const [items] = await getLeadTargetsByQuiz(quiz.backendId);
|
||||
const existing = (items ?? []).filter((t) => t.type === "webhook");
|
||||
console.log("Saving flow -> existing webhook targets:", existing);
|
||||
|
||||
if (!target) {
|
||||
await deleteLeadTargetsByQuizType(quiz.backendId, "webhook");
|
||||
// Пустое значение — удаляем все
|
||||
const deletePromises = existing.map((t) => deleteLeadTarget(t.id));
|
||||
await Promise.all(deletePromises);
|
||||
enqueueSnackbar("Webhook удален", { variant: "success" });
|
||||
} else if (existing.length > 0) {
|
||||
// Уже существует — обновляем первый и удаляем все лишние
|
||||
const [first, ...extra] = existing;
|
||||
await Promise.all([
|
||||
updateLeadTarget({ id: first.id, target }),
|
||||
...extra.map((t) => deleteLeadTarget(t.id)),
|
||||
]);
|
||||
enqueueSnackbar("Webhook обновлен", { variant: "success" });
|
||||
} else {
|
||||
// Не существует — создаем
|
||||
await createLeadTarget({
|
||||
type: "webhook",
|
||||
quizID: quiz.backendId,
|
||||
@ -56,6 +59,8 @@ export const ZapierModal: FC<ZapierModalProps> = ({
|
||||
});
|
||||
enqueueSnackbar("Webhook сохранен", { variant: "success" });
|
||||
}
|
||||
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
enqueueSnackbar("Ошибка при сохранении", { variant: "error" });
|
||||
} finally {
|
||||
@ -63,11 +68,18 @@ export const ZapierModal: FC<ZapierModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const { isLoading, zapierTarget, refresh } = useLeadTargets(quiz?.backendId, isModalOpen);
|
||||
|
||||
const formik = useFormik<{ webhookUrl: string }>({
|
||||
initialValues: { webhookUrl: "" },
|
||||
initialValues: { webhookUrl: zapierTarget?.target ?? "" },
|
||||
onSubmit: handleSubmit,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
formik.setFieldValue("webhookUrl", zapierTarget?.target ?? "");
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [zapierTarget?.target]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isModalOpen) {
|
||||
setTimeout(() => {
|
||||
@ -160,6 +172,9 @@ export const ZapierModal: FC<ZapierModalProps> = ({
|
||||
gap: isMobile ? "10px" : "38px"
|
||||
}}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Box sx={{ width: "100%", height: 44, borderRadius: "8px", bgcolor: "action.hover" }} />
|
||||
) : (
|
||||
<CustomTextField
|
||||
id="zapier-webhook-url"
|
||||
placeholder="введите url здесь"
|
||||
@ -167,8 +182,9 @@ export const ZapierModal: FC<ZapierModalProps> = ({
|
||||
onChange={(e) => formik.setFieldValue("webhookUrl", e.target.value)}
|
||||
maxLength={150}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
disabled={isSaving}
|
||||
disabled={isSaving || isLoading}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
sx={{
|
||||
|
||||
@ -5,7 +5,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { useQuizStore } from "@root/quizes/store";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { PartnersBoard } from "./PartnersBoard/PartnersBoard";
|
||||
import { getLeadTargetsByQuiz } from "@/api/leadtarget";
|
||||
import { getLeadTargetsByQuiz, LeadTargetModel } from "@/api/leadtarget";
|
||||
import { QuizMetricType } from "@model/quizSettings";
|
||||
|
||||
interface IntegrationsPageProps {
|
||||
@ -31,7 +31,9 @@ export const IntegrationsPage = ({
|
||||
const [isZapierModalOpen, setIsZapierModalOpen] = useState<boolean>(false);
|
||||
const [isPostbackModalOpen, setIsPostbackModalOpen] = useState<boolean>(false);
|
||||
const [leadTargetsLoaded, setLeadTargetsLoaded] = useState<boolean>(false);
|
||||
const [leadTargets, setLeadTargets] = useState<any[] | null>(null);
|
||||
const [leadTargets, setLeadTargets] = useState<LeadTargetModel[] | null>(null);
|
||||
const [zapierTarget, setZapierTarget] = useState<LeadTargetModel | null>(null);
|
||||
const [postbackTarget, setPostbackTarget] = useState<LeadTargetModel | null>(null);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@ -39,16 +41,34 @@ export const IntegrationsPage = ({
|
||||
}, [navigate, editQuizId]);
|
||||
|
||||
useEffect(() => {
|
||||
// Загрузка связанных с квизом данных интеграций при входе на страницу
|
||||
const load = async () => {
|
||||
if (!leadTargetsLoaded && quiz?.id) {
|
||||
const [items] = await getLeadTargetsByQuiz(quiz.backendId);
|
||||
setLeadTargets(items ?? []);
|
||||
const list = items ?? [];
|
||||
console.log("LeadTargets fetched:", list);
|
||||
setLeadTargets(list);
|
||||
const webhookOnly = list.filter((t) => t.type === "webhook");
|
||||
console.log("Webhook-only targets:", webhookOnly);
|
||||
const zapier = webhookOnly.find((t) => (t.target || "").toLowerCase().includes("zapier")) ?? null;
|
||||
const postback = webhookOnly.find((t) => (t.target || "").toLowerCase().includes("postback")) ?? null;
|
||||
console.log("Distributed targets:", { zapier, postback });
|
||||
setZapierTarget(zapier);
|
||||
setPostbackTarget(postback);
|
||||
setLeadTargetsLoaded(true);
|
||||
}
|
||||
};
|
||||
load();
|
||||
}, [leadTargetsLoaded, quiz?.id]);
|
||||
|
||||
const refreshLeadTargets = async () => {
|
||||
if (!quiz?.id) return;
|
||||
const [items] = await getLeadTargetsByQuiz(quiz.backendId);
|
||||
const list = items ?? [];
|
||||
setLeadTargets(list);
|
||||
const webhookOnly = list.filter((t) => t.type === "webhook");
|
||||
setZapierTarget(webhookOnly.find((t) => (t.target || "").toLowerCase().includes("zapier")) ?? null);
|
||||
setPostbackTarget(webhookOnly.find((t) => (t.target || "").toLowerCase().includes("postback")) ?? null);
|
||||
};
|
||||
const heightBar = heightSidebar + 51 + 88 + 36 + 25;
|
||||
|
||||
if (quiz === undefined)
|
||||
@ -103,6 +123,8 @@ export const IntegrationsPage = ({
|
||||
setIsPostbackModalOpen={setIsPostbackModalOpen}
|
||||
isPostbackModalOpen={isPostbackModalOpen}
|
||||
handleClosePostbackModal={handleClosePostbackModal}
|
||||
zapierTarget={zapierTarget}
|
||||
postbackTarget={postbackTarget}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@ -7,6 +7,7 @@ import { YandexMetricaLogo } from "../mocks/YandexMetricaLogo";
|
||||
import { VKPixelLogo } from "../mocks/VKPixelLogo";
|
||||
import { QuizMetricType } from "@model/quizSettings";
|
||||
import { AmoCRMLogo } from "../mocks/AmoCRMLogo";
|
||||
import type { LeadTargetModel } from "@/api/leadtarget";
|
||||
import { useCurrentQuiz } from "@/stores/quizes/hooks";
|
||||
|
||||
const AnalyticsModal = lazy(() =>
|
||||
@ -48,6 +49,8 @@ type PartnersBoardProps = {
|
||||
setIsPostbackModalOpen: (value: boolean) => void;
|
||||
isPostbackModalOpen: boolean;
|
||||
handleClosePostbackModal: () => void;
|
||||
zapierTarget?: LeadTargetModel | null;
|
||||
postbackTarget?: LeadTargetModel | null;
|
||||
};
|
||||
|
||||
export const PartnersBoard: FC<PartnersBoardProps> = ({
|
||||
@ -65,6 +68,8 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
||||
setIsPostbackModalOpen,
|
||||
isPostbackModalOpen,
|
||||
handleClosePostbackModal,
|
||||
zapierTarget,
|
||||
postbackTarget,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const quiz = useCurrentQuiz();
|
||||
@ -173,6 +178,7 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
||||
handleCloseModal={handleCloseZapierModal}
|
||||
companyName={companyName}
|
||||
quiz={quiz!}
|
||||
currentTarget={zapierTarget ?? null}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
@ -183,6 +189,7 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
||||
handleCloseModal={handleClosePostbackModal}
|
||||
companyName={companyName}
|
||||
quiz={quiz!}
|
||||
currentTarget={postbackTarget ?? null}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
|
||||
50
src/pages/IntegrationsPage/hooks/useLeadTargets.ts
Normal file
50
src/pages/IntegrationsPage/hooks/useLeadTargets.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { getLeadTargetsByQuiz, LeadTargetModel } from "@/api/leadtarget";
|
||||
|
||||
type UseLeadTargetsResult = {
|
||||
isLoading: boolean;
|
||||
zapierTarget: LeadTargetModel | null;
|
||||
postbackTarget: LeadTargetModel | null;
|
||||
refresh: () => Promise<void>;
|
||||
};
|
||||
|
||||
export function useLeadTargets(quizBackendId: number | undefined, isOpen: boolean): UseLeadTargetsResult {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [zapierTarget, setZapierTarget] = useState<LeadTargetModel | null>(null);
|
||||
const [postbackTarget, setPostbackTarget] = useState<LeadTargetModel | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (!quizBackendId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const [items] = await getLeadTargetsByQuiz(quizBackendId);
|
||||
const list = items ?? [];
|
||||
console.log("LeadTargets fetched:", list);
|
||||
const webhookOnly = list.filter((t) => t.type === "webhook");
|
||||
console.log("Webhook-only targets:", webhookOnly);
|
||||
const zapier = webhookOnly.find((t) => (t.target || "").toLowerCase().includes("zapier")) ?? null;
|
||||
const postback = webhookOnly.find((t) => (t.target || "").toLowerCase().includes("postback")) ?? null;
|
||||
console.log("Distributed targets:", { zapier, postback });
|
||||
setZapierTarget(zapier);
|
||||
setPostbackTarget(postback);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [quizBackendId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
load();
|
||||
}
|
||||
}, [isOpen, load]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
zapierTarget,
|
||||
postbackTarget,
|
||||
refresh: load,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user