Product Catalog Block
import React, { useMemo, useState } from "react"; import { Check, Circle, Square, CircleDashed, Shapes, Sparkles, Shield, CalendarDays, Package, Upload, Image as ImageIcon, Wand2, Ruler, Info, ChevronRight, ChevronLeft, Percent } from "lucide-react"; // ============================= // STCKRBOX — Новый UI заказа // 3 шага + липкий сайдбар, моб. панель, табы «Макет» // Без изменения логики/функций: только UI/UX // ============================= // Вспомогательные типы const shapes = [ { id: "circle", name: "Круг", icon: Circle }, { id: "square", name: "Квадрат", icon: Square }, { id: "oval", name: "Овал", icon: CircleDashed }, { id: "rect", name: "Прямоугольник", icon: Ruler }, { id: "custom", name: "Фигурная", icon: Shapes }, ] as const; const materialList = [ { id: "white-gloss", title: "Белая глянцевая", tagline: "яркие цвета, классика брендинга", thumbnail: "data:image/svg+xml;utf8,", multiplier: 1, }, { id: "white-matte", title: "Белая матовая", tagline: "мягкое свечение, без бликов", thumbnail: "data:image/svg+xml;utf8,", multiplier: 1.05, }, { id: "clear", title: "Прозрачная", tagline: "видно фон, отлично для стекла", thumbnail: "data:image/svg+xml;utf8,", multiplier: 1.12, }, { id: "holo", title: "Голографическая", tagline: "вау-эффект мерцания", thumbnail: "data:image/svg+xml;utf8,", multiplier: 1.35, }, ] as const; const laminationOptions = [ { id: "none", title: "Без", mult: 1 }, { id: "gloss", title: "Глянец", mult: 1.1 }, { id: "matte", title: "Мат", mult: 1.1 }, ] as const; const qtyChips = [50, 100, 250, 500, 1000, 2000] as const; const deadlineOptions = ( todayLabel: string, fasterLabel: string, econLabel: string ) => [ { id: "std", label: todayLabel, subtitle: "Стандарт", priceMult: 1 }, { id: "fast", label: fasterLabel, subtitle: "Быстрее", priceMult: 1.2 }, { id: "econ", label: econLabel, subtitle: "Эконом", priceMult: 0.9 }, ]; // Утилита форматирования цены const fmt = (n: number) => new Intl.NumberFormat("ru-RU", { style: "currency", currency: "RUB", maximumFractionDigits: 0 }).format(n); export default function StickerOrderUI() { // ======= Состояние ======= const [step, setStep] = useState(1); const [shape, setShape] = useState<(typeof shapes)[number]["id"]>("circle"); const [width, setWidth] = useState(50); // мм const [height, setHeight] = useState(50); // мм const [material, setMaterial] = useState<(typeof materialList)[number]["id"]>("white-gloss"); const [lamination, setLamination] = useState<(typeof laminationOptions)[number]["id"]>("none"); const [qty, setQty] = useState(100); const [deadline, setDeadline] = useState("std"); const [designTab, setDesignTab] = useState<"upload" | "constructor" | "templates">("upload"); // ======= Дата для карточек сроков ======= const dates = useMemo(() => { const now = new Date(); const d1 = new Date(now); d1.setDate(d1.getDate() + 2); // Стандарт const d2 = new Date(now); d2.setDate(d2.getDate() + 1); // Быстрее const d3 = new Date(now); d3.setDate(d3.getDate() + 5); // Эконом const f = (d: Date) => d.toLocaleDateString("ru-RU", { day: "2-digit", month: "short" }).replace(".", ""); return deadlineOptions(`Готово к ${f(d1)}`, `Готово к ${f(d2)}`, `Готово к ${f(d3)}`); }, []); // ======= «Расчёт» (упрощённый для демо UI) ======= const baseArea = Math.max(10, width) * Math.max(10, height); // мм² const areaCoeff = Math.max(1, baseArea / (50 * 50)); const materialMult = materialList.find((m) => m.id === material)?.multiplier ?? 1; const laminationMult = laminationOptions.find((l) => l.id === lamination)?.mult ?? 1; const deadlineMult = dates.find((d) => d.id === deadline)?.priceMult ?? 1; const unitBase = 6; // Условная базовая цена за шт. const unitPrice = Math.round(unitBase * areaCoeff * materialMult * laminationMult); const total = Math.round(unitPrice * qty * deadlineMult); const perUnit = total / qty; // ======= Пресеты размеров ======= const presets = [ [30, 30], [50, 50], [100, 100], [105, 148], // A6 [148, 210], // A5 ]; // ======= Валидация ======= const sizeError = width < 10 || height < 10 || width > 1000 || height > 1000; return (
{/* Page */} {/* Header */}
{/* LEFT: Steps */}
{/* Stepper */} {/* Step 1 */} {step === 1 && (

Шаг 1. Форма и размер

Укажите форму и габариты наклейки

{/* Shapes */}
{shapes.map((s) => { const Icon = s.icon; const active = shape === s.id; return ( ); })}
{/* Size inputs */}
setWidth(Number(e.target.value))} className={`w-full rounded-xl border px-3 py-2 outline-none focus:ring-2 transition ${ sizeError ? "border-rose-400 focus:ring-rose-100" : "border-slate-300 focus:ring-slate-200" }`} aria-invalid={sizeError} />
setHeight(Number(e.target.value))} className={`w-full rounded-xl border px-3 py-2 outline-none focus:ring-2 transition ${ sizeError ? "border-rose-400 focus:ring-rose-100" : "border-slate-300 focus:ring-slate-200" }`} aria-invalid={sizeError} />
{presets.map(([w, h], idx) => ( ))}
{sizeError ? (
Диапазон 10–1000 мм. Проверьте значения.
) : (
Размер корректный
)}
)} {/* Step 2 */} {step === 2 && (

Шаг 2. Материал и защита

Выберите базу и ламинацию для условий использования

{materialList.map((m) => ( ))}
{/* Lamination */}
Ламинация
{laminationOptions.map((l) => ( ))}
Ламинация защищает от влаги и царапин, продлевает срок службы на 6–12 мес.
)} {/* Step 3 */} {step === 3 && (

Шаг 3. Тираж и срок

Сколько штук и когда нужно?

{/* Quantity */}
setQty(Number(e.target.value))} className="w-full" aria-label="Тираж" />
setQty(Math.min(3000, Math.max(1, Number(e.target.value))))} className="w-full rounded-xl border px-3 py-2 border-slate-300" aria-label="Количество" />
{qtyChips.map((q) => ( ))}
{/* Deadlines */}
{dates.map((d) => ( ))}
)} {/* Design Tabs */}
{designTab === "upload" && (
Перетащите файл сюда или
PDF, AI, EPS, SVG, PNG (300 dpi). Проверка макета — бесплатно, подскажем если что-то не так.
)} {designTab === "constructor" && (
Создайте наклейку с нуля
)} {designTab === "templates" && (
Выберите шаблон и отредактируйте цвета/текст
)}
{/* RIGHT: Sticky Summary */}
{/* Mobile bottom bar */}
{fmt(total)}
Готово {dates.find(d=>d.id===deadline)?.label.replace("Готово ", "к ")}
{/* Footer trust line */}
⭐️⭐️⭐️⭐️⭐️ 4.9 / 2 130 отзывов • Сделано сегодня: 38 заказов
); } function SummaryRow({ label, value, onEdit }: { label: string; value: string; onEdit: () => void }) { return (
{label}
{value}
); } /* ============================================================ STCKRBOX — ПЛАТФОРМЕННЫЙ АДАПТЕР (vanilla JS + CSS) ============================================================ Цель: применить новый UI на существующей странице КАЛЬКУЛЯТОРА без изменения бэкенда, имён полей, валидации и логики расчёта. Работаем поверх готовой верстки: переставляем блоки, даём стили, добавляем «3 шага», липкий сайдбар и мобильную нижнюю панель. КАК ВСТАВИТЬ: 1) На странице товара/калькулятора добавьте

Стикеры

Форма
Материал
Ламинация
Тираж шт.
Тираж (шт)
Срок изгот.
22.10.25
Срок изгот.
23.10.25
Срок изгот.
27.10.25
Итог