import { useCallback, useEffect, useMemo, useState } from 'react'; import styled, {css} from 'styled-components'; import {clearCart, loadInitialData, setOption} from './api/promoApi'; import { Cart } from './components/Cart/Cart'; import { CookiePopup } from './components/CookiePopup/CookiePopup'; import { FamilySection } from './components/Family/FamilySection'; import { CategoriesSection } from './components/Categories/CategoriesSection'; import { DaysSection } from './components/Days/DaysSection'; import { Accordion } from './components/Accordion/Accordion'; import { normalizeProductImage } from './utils/product'; import type { InitialData } from './types/api'; import type { Product } from './types/cart'; import type { User } from './types/user'; import CustomSlider from "./components/Fullness/FullnessSlider"; import {useGlobalZustandState} from "./hooks/useGlobalZustandState"; import {setUsers, useAppDispatch, useAppSelector} from "./store"; import {FinitaLaCommedia} from "./components/Cart/FinitaLaCommedia"; import {reachGoal} from "./hooks/useYM"; export function App() { const [initialData, setInitialData] = useState(null); const [products, setProducts] = useState([]); const [isCartComplete, setIsCartComplete] = useState(false); const [showEmptyPopup, setShowEmptyPopup] = useState(false); const [isCartLoading, setIsCartLoading] = useState(true); const [showLoader, setShowLoader] = useState(false); const [openSection, setOpenSection] = useState('family'); const [needRefresh, setNeedRefresh] = useState(true); const [isFullnessHighlighted, setIsFullnessHighlighted] = useState(false); const [mobileShowFilters, setMobileShowFilters] = useState(true); const {setInitial, precision} = useGlobalZustandState((state) => state); const dispatch = useAppDispatch(); const users = useAppSelector(state => state.family.users); const loadInitialState = async () => { setShowLoader(true); loadInitialData().then(data => { setInitialData(data); dispatch(setUsers(data.users)); setProducts(data.cart.flat().map(product => normalizeProductImage(product))); setInitial(false, data.excess || 0, data.precision || 0); setIsCartComplete(false) }).finally(() => { setShowLoader(false); }); }; const emptyCart = async () => { return clearCart().finally(() => { return loadInitialState(); }); }; useEffect(() => { if (needRefresh) { setNeedRefresh(false); loadInitialState(); } }, [needRefresh, setNeedRefresh, loadInitialState]); const categories = initialData?.available_groups ?? []; const selectedCategories = initialData?.initial_groups ?? []; const days = initialData?.days ?? 7; const handleDaysChange = useCallback(async (nextDays: number) => { setShowLoader(true); setOption('days', nextDays) .finally(() => { loadInitialState(); }); }, [loadInitialState]); const handleUsersChange = useCallback(async (nextUsers: User[]) => { setShowLoader(true); setOption('users', nextUsers) .finally(() => { loadInitialState(); }); }, [loadInitialState]); const handleCategoriesChange = useCallback(async (nextCategories: string[]) => { setShowLoader(true); setOption('initial_groups', nextCategories) .finally(() => { loadInitialState(); }); }, [loadInitialState]); const handlePercentChange = useCallback(async (fullness: number) => { setShowLoader(true); setOption('fullness', fullness) .finally(() => { loadInitialState(); }) }, [loadInitialState]); useEffect(() => { if (isCartComplete && !isCartLoading && precision < 80) { setShowEmptyPopup(true); } else { setShowEmptyPopup(false); } }, [isCartComplete, isCartLoading, precision, setShowEmptyPopup]); useEffect(() => { reachGoal('ac_open_settings_tab_' + openSection); }, [openSection]) const pageContent = useMemo(() => { if (!initialData) { return Загрузка...; } return ( {showLoader &&
}

Хватило

setMobileShowFilters(true)}>

Заботимся о балансе нутриентов

{ const newProducts = typeof valueOrArrUpdater === 'function' ? valueOrArrUpdater(products) : valueOrArrUpdater; if (newProducts.length < products.length) { setIsCartComplete(false); setTimeout(() => { setIsCartComplete(false); }, 3000); } setProducts(newProducts); }} onComplete={() => { setIsCartComplete(true); setIsCartLoading(false); }} onLoadingChange={setIsCartLoading} onMistake={() => setNeedRefresh(true)} />

Настройте корзину под себя

setMobileShowFilters(false)}>

Чем больше вы расскажете, тем точнее будет подбор

} isOpen={openSection === 'days'} onToggle={() => setOpenSection(openSection === 'days' ? '' : 'days')} > 0 ? `${users.length} человек` : 'Не указаны' } icon={семья} isOpen={openSection === 'family'} onToggle={() => setOpenSection(openSection === 'family' ? '' : 'family')} > 0 ? `Выбраны ${selectedCategories.length}` : 'Не указаны' } isHighlighted={selectedCategories.length > 0} isOpen={openSection === 'preferences'} onToggle={() => setOpenSection(openSection === 'preferences' ? '' : 'preferences')} > setNeedRefresh(true)} /> } description={openSection === 'fullness' ? 'Чем меньше значение, тем меньше продуктов и калорий в корзине' : initialData.fullness + '% — ваша цель. Результат может варьироваться'} isHighlighted={isFullnessHighlighted} isOpen={openSection === 'fullness'} onToggle={() => { if (openSection !== 'fullness') { setIsFullnessHighlighted(true); } setOpenSection(openSection === 'fullness' ? '' : 'fullness') }} >
Процент влияет на количество калорий, базовый рацион — 100%, если часть еды покупается и съедается за пределами дома — подвиньте ползунок влево, если вы ждёте гостей — вправо
); }, [ categories, days, handleCategoriesChange, handleDaysChange, handleUsersChange, initialData, isCartComplete, isCartLoading, openSection, products, selectedCategories.length, users, ]); return ( <> {pageContent} {showEmptyPopup && setShowEmptyPopup(false)} onRefresh={emptyCart} />} ); } const Overlay = styled.div` position: fixed; display: flex; inset: 0; background-color: rgba(255, 255, 255, 0.1); justify-content: center; align-items: center; z-index: 9; overflow: auto; height: 100vh; div { --color-1: #ffa0a0; --size: 1.2px; width: calc(48 * var(--size)); height: calc(48 * var(--size)); border: calc(5 * var(--size)) solid var(--color-1); border-bottom-color: transparent; border-radius: 50%; display: inline-block; box-sizing: border-box; animation: rotation 1s linear infinite; } @keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; const LoadingPage = styled.div` min-height: 100vh; display: flex; align-items: center; justify-content: center; color: ${({ theme }) => theme.colors.white}; font-size: 20px; `; const Container = styled.main<{$fullHeight: boolean}>` position: relative; display: flex; width: calc(100% - 40px); max-width: 1228px; height: 90vh; margin: 40px auto 0; background: ${({ theme }) => theme.colors.white}; border-radius: ${({ theme }) => theme.radius.page}; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04); overflow: hidden; @media (max-width: 900px) { flex-direction: column; height: ${$fullHeight => $fullHeight ? 'auto' : '90vh'}; } ${({ theme }) => theme.media.mobile(css<{$fullHeight: boolean}>` margin: 0; width: 100%; h1 { padding-top: 10px; } height: ${({ $fullHeight }) => $fullHeight ? 'auto' : '100%'}; border-radius: ${({ $fullHeight }) => $fullHeight ? '0 0 8px 8px' : '0'}; `)} `; const Column = styled.section` padding: 40px; flex: 1; width: 50%; overflow-y: auto; & + & { border-left: 1px solid #f0f0f0; } @media (max-width: 900px) { width: 100%; padding: 20px; & + & { border-left: none; } } `; const LeftColumn = styled(Column)<{$dataShow: boolean}>` display: flex; flex-direction: column; height: 100%; max-width: 490px; position: relative; ${props => props.theme.media.mobile(css` display: ${props.$dataShow ? 'flex' : 'none'}; `)} `; const RightColumn = styled(Column)<{$dataShow: boolean}>` ${({ theme }) => theme.media.mobile(css` display: none; `)} ${props => props.theme.media.mobile(css` display: ${props.$dataShow ? 'flex' : 'none'}; flex-direction: column; `)} `; const TitleBlock = styled.div` margin-bottom: 32px; position: relative; h1 { font-size: 32px; line-height: 38px; font-weight: 700; color: ${({ theme }) => theme.colors.darkGreen}; margin-bottom: 8px; } p { font-size: 16px; line-height: 19px; color: ${({ theme }) => theme.colors.gray}; font-weight: 350; } `; const FilterTitle = styled.div` position: relative; margin-bottom: 40px; h2 { font-size: 20px; line-height: 24px; margin-bottom: 8px; color: ${({ theme }) => theme.colors.darkGreen}; } p { font-size: 16px; line-height: 19px; color: ${({ theme }) => theme.colors.gray}; font-weight: 350; } ${({ theme }) => theme.media.mobile(css` margin-bottom: 32px; h2 { padding-top: 10px; text-align: center; font-size: 16px; line-height: 19px; } p { font-size: 14px; line-height: 16px; max-width: 271px; text-align: center; margin: 0 auto; } `)} `; const SettingsPanel = styled.div` border: 1px solid ${({ theme }) => theme.colors.border}; border-radius: 12px; `; const Alert = styled.div` gap: 8px; display: flex; flex-direction: row; font-weight: 370; background-color: #F1FAF0; border-radius: 8px; padding: 12px; svg { width: 24px; } span { font-weight: 450; color: #5EA12D; } ` const MobileFilter = styled.div` display: none; position: absolute; top:0; right:0; width: 40px; height: 40px; justify-content: center; align-items: center; ${({ theme }) => theme.media.mobile(css` display: flex; `)} ` const MobileBack = styled.div` display: none; position: absolute; top:0; left:0; width: 40px; height: 40px; justify-content: center; align-items: center; ${({ theme }) => theme.media.mobile(css` display: flex; `)} `