Snapshot of uncommitted work: landing, bots, prod infra, partner cabinet, backoffice stage 1
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
45d7512d2b
commit
d452824c62
314 files changed
+44394
-121
No files matched your search
@@ -0,0 +1,151 @@
|
||||
'use client';
|
||||
|
||||
import { Icon } from '@barev/ui';
|
||||
import { useEffect, useState } from 'react';
|
||||
import imgL from '../assets/flowers/l.jpg';
|
||||
import imgM from '../assets/flowers/m.jpg';
|
||||
import imgS from '../assets/flowers/s.jpg';
|
||||
import { BotLink } from './client';
|
||||
|
||||
/** Цены за одну доставку; при еженедельной подписке — своя цена (≈ −10%) */
|
||||
const PLANS = [
|
||||
{
|
||||
id: 's',
|
||||
letter: 'S',
|
||||
name: 'Мини',
|
||||
stems: 10,
|
||||
stemsLabel: '9–11 стеблей',
|
||||
photo: imgS,
|
||||
alt: 'Букет S: розовые кустовые розы, белая альстромерия и эвкалипт в крафте',
|
||||
base: 9900,
|
||||
week: 8900,
|
||||
salon: '13–15 тыс. ֏',
|
||||
features: ['Сезонный микс от флориста', 'Для кухни или рабочего стола, ~35 см', 'Крафт-упаковка и подкормка'],
|
||||
},
|
||||
{
|
||||
id: 'm',
|
||||
letter: 'M',
|
||||
name: 'Классика',
|
||||
stems: 16,
|
||||
stemsLabel: '15–17 стеблей',
|
||||
photo: imgM,
|
||||
alt: 'Букет M: персиковые пионовидные розы, сиреневая эустома и белые хризантемы в крафте',
|
||||
base: 14900,
|
||||
week: 13400,
|
||||
salon: '19–23 тыс. ֏',
|
||||
features: ['Сезонный микс с акцентными цветами', 'Для гостиной, ~45 см', 'Ваза к первому букету — остаётся у вас'],
|
||||
},
|
||||
{
|
||||
id: 'l',
|
||||
letter: 'L',
|
||||
name: 'Пышный',
|
||||
stems: 27,
|
||||
stemsLabel: '25–30 стеблей',
|
||||
photo: imgL,
|
||||
alt: 'Букет L: розовые розы, белая гортензия, ранункулюсы и эустома в крафте',
|
||||
base: 22900,
|
||||
week: 20600,
|
||||
salon: '30–42 тыс. ֏',
|
||||
features: ['Эустома, пионовидные розы и другие премиальные сорта', 'Заметен с порога, ~55 см', 'Ваза к первому букету — остаётся у вас'],
|
||||
},
|
||||
] as const;
|
||||
|
||||
const FREQ = [
|
||||
{ id: 'week', label: 'Каждую неделю', perMonth: 4.3 },
|
||||
{ id: 'biweek', label: 'Раз в 2 недели', perMonth: 2.15 },
|
||||
{ id: 'month', label: 'Раз в месяц', perMonth: 1 },
|
||||
] as const;
|
||||
type FreqId = (typeof FREQ)[number]['id'];
|
||||
|
||||
const amd = (n: number) => `${Math.round(n).toLocaleString('ru-RU')} ֏`;
|
||||
const round10 = (n: number) => Math.round(n / 10) * 10;
|
||||
const round100 = (n: number) => Math.round(n / 100) * 100;
|
||||
|
||||
/** Тарифы S/M/L как клубные карты главной; частота меняет цену и «≈ в месяц» */
|
||||
export function FlowerPlans() {
|
||||
const [freq, setFreq] = useState<FreqId>('biweek');
|
||||
const f = FREQ.find((x) => x.id === freq)!;
|
||||
|
||||
return (
|
||||
<section id="plans" className="h-plans fw-plans" aria-labelledby="plans-title">
|
||||
<div className="h-section-head">
|
||||
<h2 id="plans-title">Выберите свой букет</h2>
|
||||
<p>Цена за одну доставку, доставка по Еревану уже включена. Без договоров: пауза и отмена в один клик.</p>
|
||||
</div>
|
||||
|
||||
<div className="fw-freq" role="radiogroup" aria-label="Как часто привозить">
|
||||
{FREQ.map((x) => (
|
||||
<button key={x.id} type="button" role="radio" aria-checked={freq === x.id} onClick={() => setFreq(x.id)}>
|
||||
{x.label}
|
||||
{x.id === 'week' && <span className="fw-freq__save">−10%</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="h-plans__grid">
|
||||
{PLANS.map((p) => {
|
||||
const price = freq === 'week' ? p.week : p.base;
|
||||
const monthly = price * f.perMonth;
|
||||
const saving = (p.base - p.week) * f.perMonth;
|
||||
return (
|
||||
<article key={p.id} className="h-plan fw-plan" data-size={p.id}>
|
||||
{p.id === 'm' && <span className="fw-plan__badge">Выбирают чаще всего</span>}
|
||||
<img className="fw-plan__photo" src={p.photo.src} width={p.photo.width} height={p.photo.height} alt={p.alt} loading="lazy" decoding="async" />
|
||||
<div className="h-card fw-card">
|
||||
<span className="h-card__club">Barev Club · Цветы</span>
|
||||
<span className="fw-card__letter" aria-hidden="true">
|
||||
{p.letter}
|
||||
</span>
|
||||
<h3 className="h-card__name">{p.name}</h3>
|
||||
<span className="h-card__price">
|
||||
{amd(price)} <small>/ букет</small>
|
||||
</span>
|
||||
</div>
|
||||
<div className="fw-plan__facts">
|
||||
<span>{p.stemsLabel}</span>
|
||||
<span>≈ {amd(price / p.stems)} за стебель</span>
|
||||
<span>≈ {amd(round10(monthly / 30))} в день</span>
|
||||
</div>
|
||||
<p className="fw-plan__anchor">
|
||||
В салоне похожий букет — <s>{p.salon}</s> + доставка
|
||||
</p>
|
||||
<ul className="h-plan__list">
|
||||
{p.features.map((x) => (
|
||||
<li key={x}>
|
||||
<Icon name="check" size={18} strokeWidth={2.6} />
|
||||
{x}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="fw-plan__month">
|
||||
{freq === 'month' ? 'Одна доставка в месяц' : `≈ ${amd(round100(monthly))} в месяц`}
|
||||
{freq === 'week' && <b> · экономия {amd(round100(saving))} в месяц</b>}
|
||||
</p>
|
||||
<BotLink className="bc-btn h-plan__cta">Оформить {p.letter}</BotLink>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p className="fw-plans__note">Оплата картой ArCa, Visa или Mastercard — за 2 дня до каждой доставки. Предоплаты за месяц нет.</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
/** Ближайшая доставка: через 2 дня, если оформить до 20:00, иначе через 3; в воскресенье не возим. Считается в браузере — страница кешируется */
|
||||
export function NextDelivery() {
|
||||
const [text, setText] = useState<{ date: string; when: string } | null>(null);
|
||||
useEffect(() => {
|
||||
const now = new Date();
|
||||
const early = now.getHours() < 20;
|
||||
const d = new Date(now);
|
||||
d.setDate(d.getDate() + (early ? 2 : 3));
|
||||
if (d.getDay() === 0) d.setDate(d.getDate() + 1);
|
||||
setText({ date: d.toLocaleDateString('ru-RU', { weekday: 'long', day: 'numeric', month: 'long' }), when: early ? 'сегодня' : 'завтра' });
|
||||
}, []);
|
||||
if (!text) return null;
|
||||
return (
|
||||
<p className="fw-next">
|
||||
<Icon name="clock" size={18} /> Ближайшая доставка — <b>{text.date}</b>, если оформить {text.when} до 20:00
|
||||
</p>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user