marketing/src/hooks/useCartPolling.ts

211 lines
11 KiB
TypeScript
Raw Normal View History

2026-08-24 10:07:16 +00:00
import {Dispatch, SetStateAction, useEffect, useRef} from 'react';
import { getCartUpdates } from '../api/promoApi';
import { normalizeProductImage } from '../utils/product';
import type { Product } from '../types/cart';
interface UseCartPollingParams {
enabled: boolean;
onProductsChange: Dispatch<SetStateAction<Product[]>>;
onComplete: () => void;
onLoadingChange: (isLoading: boolean) => void;
setDisplayText: Dispatch<SetStateAction<string>>;
}
2026-08-28 06:41:34 +00:00
let missing_scenarios = [
[
[10_000, 'Ищем Брока...'],
[30_000, 'Брок сбежал из супермаркета...'],
[30_000, 'Мы всё ещё пытаемся его найти...'],
[10_000, 'Может быть он в колбасном отделе?'],
[10_000, 'Или в своём любимом?'],
[10_000, 'Какой у Брока любимый отдел?'],
[10_000, 'Если это прятки, то я проиграл, выходи'],
[15_000, 'Я сдаюсь, перезагрузите страницу?'],
],
[
[10_000, 'Брок у полки с соком...'],
[20_000, 'Он выбирает апельсиновый...'],
[15_000, 'Вспомнил, что любит яблочный...'],
[25_000, 'Теперь сравнивает состав...'],
[20_000, 'Сока нет, есть нектар...'],
[15_000, 'Брок возмущён...'],
[20_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, 'Брок обнаружил тележку с крутящимся передним колесом...'],
[20_000, 'Теперь он использует её как скейтборд...'],
[30_000, 'Брок проехал мимо отдела напитков...'],
[15_000, 'Сотрудник делает замечание...'],
[15_000, 'Брок не слышит — он в наушниках...'],
[20_000, 'Брок врезался в пирамиду из консервов...'],
[25_000, 'Консервы разлетелись'],
[15_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;
2026-08-24 10:07:16 +00:00
export function useCartPolling({
enabled,
onProductsChange,
onComplete,
onLoadingChange,
setDisplayText,
}: UseCartPollingParams) {
const timeoutRef = useRef<number | null>(null);
useEffect(() => {
if (!enabled) return;
let cancelled = false;
2026-08-28 06:41:34 +00:00
let missing_timeout: ReturnType<typeof setTimeout> | null = null;
let missing_scenario_index = Math.round(Math.random() * missing_scenarios.length);
let missing_scenario_element = -1;
const timeoutFunction = () => {
console.log(missing_scenario_index);
missing_scenario_element++;
setDisplayText(missing_scenarios[missing_scenario_index][missing_scenario_element][1])
missing_timeout = setTimeout(timeoutFunction, missing_scenarios[missing_scenario_index][missing_scenario_element][0]);
};
2026-08-24 10:07:16 +00:00
async function tick() {
try {
onLoadingChange(true);
const update = await getCartUpdates();
if (cancelled) return;
2026-08-28 06:41:34 +00:00
if (update.status === 'waiting') {
if (!missing_timeout) {
missing_timeout = setTimeout(() => {
setDisplayText('Ищем Брока...')
timeoutFunction();
}, 10_000);
}
setDisplayText('Брок бегает по супермаркету...')
timeoutRef.current = window.setTimeout(tick, 2000);
return;
} else if (missing_timeout) {
clearTimeout(missing_timeout);
}
2026-08-24 10:07:16 +00:00
if (update.status === 'complete') {
onLoadingChange(false);
onComplete();
return;
}
if (update.status === 'clear') {
const nextProducts = update.payload?.chunk?.map(product => normalizeProductImage(product)) ?? [];
2026-08-28 06:41:34 +00:00
timeoutRef.current = window.setTimeout(tick, 3000);
2026-08-24 10:07:16 +00:00
onProductsChange(nextProducts);
}
if (update.status === 'data') {
const chunk = update.payload?.chunk?.map(product => normalizeProductImage(product)) ?? [];
onProductsChange(current => {
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('Брок закинул пару товаров...')
}
2026-08-28 06:41:34 +00:00
timeoutRef.current = window.setTimeout(tick, 5000);
2026-08-24 10:07:16 +00:00
} catch (error) {
2026-08-28 06:41:34 +00:00
setDisplayText('Нет связи с супермаркетом...')
2026-08-24 10:07:16 +00:00
timeoutRef.current = window.setTimeout(tick, 3000);
}
}
tick();
return () => {
cancelled = true;
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current);
}
};
}, [enabled, onComplete, onLoadingChange, onProductsChange]);
}