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 (
);
}
/*
============================================================
STCKRBOX — ПЛАТФОРМЕННЫЙ АДАПТЕР (vanilla JS + CSS)
============================================================
Цель: применить новый UI на существующей странице КАЛЬКУЛЯТОРА
без изменения бэкенда, имён полей, валидации и логики расчёта.
Работаем поверх готовой верстки: переставляем блоки, даём стили,
добавляем «3 шага», липкий сайдбар и мобильную нижнюю панель.
КАК ВСТАВИТЬ:
1) На странице товара/калькулятора добавьте
{/* Page */}
{/* Header */}
{/* LEFT: Steps */}
{/* Stepper */}
{/* Step 1 */}
{step === 1 && (
)}
{/* Step 2 */}
{step === 2 && (
)}
{/* Step 3 */}
{step === 3 && (
{/* Deadlines */}
)}
{/* Design Tabs */}
{designTab === "upload" && (
)}
{designTab === "constructor" && (
)}
{designTab === "templates" && (
)}
{/* RIGHT: Sticky Summary */}
{/* Mobile bottom bar */}
{/* Footer trust line */}
);
}
function SummaryRow({ label, value, onEdit }: { label: string; value: string; onEdit: () => void }) {
return (
S
STCKRBOX
Стикеры
Заказ
Шаг 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 мм. Проверьте значения.
) : (
Шаг 2. Материал и защита
Выберите базу и ламинацию для условий использования
{materialList.map((m) => (
))}
{/* Lamination */}
Ламинация
{laminationOptions.map((l) => (
))}
Ламинация защищает от влаги и царапин, продлевает срок службы на 6–12 мес.
Шаг 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) => (
))}
{dates.map((d) => (
))}
Перетащите файл сюда или
PDF, AI, EPS, SVG, PNG (300 dpi). Проверка макета — бесплатно, подскажем если что-то не так.
Создайте наклейку с нуля
Выберите шаблон и отредактируйте цвета/текст
{fmt(total)}
Готово {dates.find(d=>d.id===deadline)?.label.replace("Готово ", "к ")}
{label}
{value}
Стикеры
Итог
Показать полное описание