marketing/src/hooks/useCartPolling.ts
2026-09-03 13:50:12 +03:00

223 lines
12 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 {Dispatch, SetStateAction, useEffect, useRef} from 'react';
import { getCartUpdates } from '../api/promoApi';
import { normalizeProductImage } from '../utils/product';
import type { Product } from '../types/cart';
import {useGlobalZustandState} from "./useGlobalZustandState";
import ym from "react-yandex-metrika";
interface UseCartPollingParams {
enabled: boolean;
onProductsChange: Dispatch<SetStateAction<Product[]>>;
onComplete: () => void;
onLoadingChange: (isLoading: boolean) => void;
setDisplayText: Dispatch<SetStateAction<string>>;
}
let missing_scenarios = [
[
[10_000, 'Ищем Брока...'],
[10_000, 'Мы всё ещё пытаемся его найти...'],
[10_000, 'Может быть он в колбасном отделе?'],
[10_000, 'Или в своём любимом?'],
[10_000, 'Какой у Брока любимый отдел?'],
[10_000, 'Если это прятки, то я проиграл, выходи'],
[10_000, 'Я сдаюсь, перезагрузите страницу?'],
],
[
[10_000, 'Брок у полки с соком...'],
[10_000, 'Он выбирает апельсиновый...'],
[10_000, 'Вспомнил, что любит яблочный...'],
[10_000, 'Теперь сравнивает состав...'],
[10_000, 'Сока нет, есть нектар...'],
[10_000, 'Брок возмущён...'],
[10_000, 'Он уходит с водой...'],
[10_000, 'Классика. Перезагрузите страницу?']
],
[
[10_000, 'Брок в молочном отделе...'],
[10_000, 'Ищет кефир 1%...'],
[10_000, 'Нашёл, но срок годности...'],
[10_000, 'Завтра. Брок не рискует...'],
[10_000, 'Берёт 2,5%...'],
[10_000, 'Потом ставит обратно...'],
[10_000, 'Пробует на ощупь ряженку...'],
[10_000, 'Это надолго. Перезагрузите страницу?']
],
[
[10_000, 'Брок в отделе замороженных продуктов...'],
[15_000, 'Брок застрял в отделе заморозки. Буквально...'],
[10_000, 'Брок пытается открыть морозилку изнутри...'],
[10_000, 'Кажется, два пломбира ему мешают...'],
[10_000, 'Вызвали спасателей и сотрудника с ломом...'],
[10_000, 'Брок заявил, что без пломбира не выйдет...'],
[10_000, 'Переговоры вступили в финальную стадию...'],
[10_000, 'У Брока коробка пломбира'],
[10_000, 'Перезагрузите страницу?'],
],
[
[10_000, 'Брок увидел акцию 1+1 и теперь не может выбрать...'],
[10_000, 'Брок сказал, что ему нужно ещё пять минуточек...'],
[10_000, 'Брок увлёкся бесплатными образцами...'],
[10_000, 'Кажется, Брок уже на КСО'],
[10_000, 'Брок забыл, что он собирает корзину?'],
[10_000, 'Брок двигается к выходу с арбузом...'],
[10_000, 'Брок отвлёкся на робот-пылесос...'],
[10_000, 'Перезагрузите страницу?'],
],
// [
// [10_000, 'Брок встал в очередь на кассу...'],
// [10_000, 'У Брока в корзине один огурец и лотерейный билет...'],
// [10_000, 'Брок завис на вопросе: «Пакет нужен?»'],
// [10_000, 'Брок спрашивает: «А у вас есть пакет с динозаврами?»'],
// [10_000, 'Брок утверждает, что видел такие пакеты в рекламе 2007 года...'],
// [10_000, 'Менеджер ищет пакеты с динозаврами на складе...'],
// [10_000, 'Это надолго, нужна перезагрузка'],
// ],
[
[10_000, 'Брок обнаружил тележку с крутящимся колесом...'],
[10_000, 'Теперь он использует её как скейтборд...'],
[10_000, 'Брок проехал мимо отдела напитков...'],
[10_000, 'Сотрудник делает замечание...'],
[10_000, 'Брок не слышит — он в наушниках...'],
[10_000, 'Брок врезался в пирамиду из консервов...'],
[10_000, 'Консервы разлетелись'],
[10_000, 'Брок помогает собирать, напевая оперу...'],
[10_000, 'Импровизированный концерт у полки с томатами.'],
[10_000, 'Перезагрузите страницу?'],
],
[
[10_000, 'Брок идёт к выходу, его останавливает охранник...'],
[10_000, 'Охранник просит показать чек...'],
[10_000, 'Брок показывает купон на скидку...'],
[10_000, 'Охранник терпеливо объясняет разницу...'],
[10_000, 'Брок делает вид, что не понимает по-русски...'],
[10_000, 'Охранник переходит на английский, французский...'],
[10_000, 'Брок отвечает на клингонском'],
[10_000, 'Охранник оказывается фанатом «Звёздного пути»'],
[10_000, 'Они уже пьют кофе и обсуждают спин-оффы.']
],
[
[10_000, 'Брок свернул не туда и оказался в отделе игрушек...'],
[10_000, 'Брок застыл перед полкой с резиновыми уточками...'],
[10_000, 'Брок нажимает на всех уточек по очереди...'],
[10_000, 'Магазин наполняется хором пищащих уток...'],
[10_000, 'Сотрудник пытается остановить, но Брок уже дирижирует...'],
[10_000, 'Покупатели подхватывают, достают уточек из корзин...'],
[10_000, 'Флешмоб пищащих уток выходит из-под контроля...'],
[10_000, 'Брок покидает отдел под овации, оставив уточку в кармане.'],
[10_000, 'Брока вернёт лишь перезагрузка страницы'],
]
] as const;
export function useCartPolling({
enabled,
onProductsChange,
onComplete,
onLoadingChange,
setDisplayText,
}: UseCartPollingParams) {
const timeoutRef = useRef<number | null>(null);
const {updateFromResponse, markAsComplete, markAsIncomplete} = useGlobalZustandState((state) => state);
useEffect(() => {
if (!enabled) return;
setDisplayText('Брок бегает по супермаркету...')
let cancelled = false;
let missing_timeout: ReturnType<typeof setTimeout> | null = null;
let missing_scenario_index = Math.round(Math.random() * (missing_scenarios.length - 1));
let missing_scenario_element = -1;
const timeoutFunction = () => {
missing_scenario_element++;
if (missing_scenario_element < missing_scenarios[missing_scenario_index].length) {
setDisplayText(missing_scenarios[missing_scenario_index][missing_scenario_element][1])
missing_timeout = setTimeout(timeoutFunction, missing_scenarios[missing_scenario_index][missing_scenario_element][0]);
}
};
async function tick() {
try {
onLoadingChange(true);
const update = await getCartUpdates();
if (cancelled) return;
if (update.status === 'waiting') {
if (!missing_timeout) {
missing_timeout = setTimeout(() => {
setDisplayText('Ищем Брока...')
timeoutFunction();
}, 10_000);
}
timeoutRef.current = window.setTimeout(tick, 2000);
return;
} else if (missing_timeout) {
clearTimeout(missing_timeout);
}
missing_scenario_element = -1;
if (update.status === 'complete') {
ym('reachGoal', 'ac_cart_complete');
onLoadingChange(false);
onComplete();
updateFromResponse(true, update.payload?.excess || 0, update.payload?.precision || 0);
return;
}
if (update.status === 'clear') {
const nextProducts = update.payload?.chunk?.map(product => normalizeProductImage(product)) ?? [];
timeoutRef.current = window.setTimeout(tick, 3000);
markAsIncomplete();
onProductsChange(nextProducts);
}
if (update.status === 'data') {
const chunk = update.payload?.chunk?.map(product => normalizeProductImage(product)) ?? [];
updateFromResponse(false, update.payload?.excess || 0, update.payload?.precision || 0);
onProductsChange((current: Product[]) => {
const next = [...current];
chunk.forEach(product => {
const index = next.findIndex(item => item.name === product.name);
if (index === -1) {
next.push(product);
} else {
next[index] = {
...next[index],
n: next[index].n + product.n,
};
}
});
return next;
});
setDisplayText('Брок закинул пару товаров...')
}
timeoutRef.current = window.setTimeout(tick, 5000);
} catch (error) {
setDisplayText('Нет связи с супермаркетом...')
timeoutRef.current = window.setTimeout(tick, 3000);
}
}
tick();
return () => {
cancelled = true;
if (missing_timeout) {
window.clearTimeout(missing_timeout);
}
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
};
}, [enabled, onComplete, onLoadingChange, onProductsChange, updateFromResponse, markAsComplete, markAsIncomplete]);
}