Postgres/Redis/RabbitMQ/MinIO/Ollama теперь общие серверы сети (адреса в .env), локально в докере остаётся только Elasticsearch + app-сервисы. Старый docker-compose.yml (полностью автономный стек) сохранён как docker-compose.selfhosted.yml.example на случай отдельного GPU-сервера в будущем. Добавлен docker-compose.prod.yml (app + свой nginx с TLS, собирающий фронтенд в статику). Makefile переписан под три режима (dev/test-up/prod). Мелкая чистка неиспользуемых импортов во фронтенде.
122 lines
4.5 KiB
TypeScript
122 lines
4.5 KiB
TypeScript
import { Check } from 'lucide-react';
|
||
import { clsx } from 'clsx';
|
||
import { PLAN_LIMITS } from '../types';
|
||
|
||
const PLAN_ORDER = ['free', 'student', 'premium', 'science'] as const;
|
||
|
||
const PLAN_FEATURES = {
|
||
free: [
|
||
'10 поисков в день',
|
||
'3 краткие изложения в месяц',
|
||
'1 проверка плагиата в месяц',
|
||
'1 задача одновременно',
|
||
'ГОСТ библиография',
|
||
],
|
||
student: [
|
||
'Безлимитный поиск',
|
||
'30 кратких изложений в месяц',
|
||
'10 проверок плагиата в месяц',
|
||
'2 задачи одновременно',
|
||
'ГОСТ библиография',
|
||
'Email уведомления',
|
||
],
|
||
premium: [
|
||
'Безлимитный поиск',
|
||
'Безлимитные изложения',
|
||
'50 проверок плагиата в месяц',
|
||
'5 задач одновременно',
|
||
'ГОСТ библиография',
|
||
'Приоритетная очередь',
|
||
],
|
||
science: [
|
||
'Безлимитный поиск',
|
||
'Безлимитные изложения',
|
||
'Безлимитные проверки плагиата',
|
||
'10 задач одновременно',
|
||
'ГОСТ библиография',
|
||
'Максимальный приоритет',
|
||
'API доступ',
|
||
],
|
||
};
|
||
|
||
const POPULAR_PLAN = 'student';
|
||
|
||
export function Pricing() {
|
||
return (
|
||
<div className="space-y-8">
|
||
<div className="text-center">
|
||
<h1 className="text-3xl font-bold text-gray-900 mb-3">Тарифные планы</h1>
|
||
<p className="text-gray-500 max-w-xl mx-auto">
|
||
Начните бесплатно. Обновите план для расширенных возможностей.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||
{PLAN_ORDER.map((planKey) => {
|
||
const plan = PLAN_LIMITS[planKey];
|
||
const features = PLAN_FEATURES[planKey];
|
||
const isPopular = planKey === POPULAR_PLAN;
|
||
|
||
return (
|
||
<div
|
||
key={planKey}
|
||
className={clsx(
|
||
'relative bg-white rounded-2xl border-2 p-6 flex flex-col',
|
||
isPopular ? 'border-brand-500 shadow-lg shadow-brand-100' : 'border-gray-100'
|
||
)}
|
||
>
|
||
{isPopular && (
|
||
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
|
||
<span className="px-3 py-1 bg-brand-600 text-white text-xs font-medium rounded-full">
|
||
Популярный
|
||
</span>
|
||
</div>
|
||
)}
|
||
|
||
<div className="mb-4">
|
||
<h3 className="text-base font-semibold text-gray-900">{plan.name}</h3>
|
||
<div className="mt-2">
|
||
{plan.price === 0 ? (
|
||
<span className="text-3xl font-bold text-gray-900">Бесплатно</span>
|
||
) : (
|
||
<>
|
||
<span className="text-3xl font-bold text-gray-900">{plan.price}₽</span>
|
||
<span className="text-gray-400 text-sm">/мес</span>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<ul className="space-y-2.5 flex-1 mb-6">
|
||
{features.map((feature) => (
|
||
<li key={feature} className="flex items-start gap-2">
|
||
<Check className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
|
||
<span className="text-sm text-gray-600">{feature}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
|
||
<button
|
||
className={clsx(
|
||
'w-full py-2.5 rounded-xl text-sm font-medium transition-colors',
|
||
isPopular
|
||
? 'bg-brand-600 text-white hover:bg-brand-700'
|
||
: plan.price === 0
|
||
? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||
: 'border border-brand-200 text-brand-700 hover:bg-brand-50'
|
||
)}
|
||
>
|
||
{plan.price === 0 ? 'Начать бесплатно' : 'Выбрать план'}
|
||
</button>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<div className="bg-gray-50 rounded-xl p-6 text-center text-sm text-gray-500">
|
||
Оплата через российские системы. При вопросах пишите на noreply@jze9.ru
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|