fix: Восстановлена генерация sitemap.xml
- Добавлен плагин автогенерации sitemap при сборке frontend - Обновлен nginx.conf для fallback на backend sitemap - Sitemap включает русские и английские версии всех страниц - Поддержка hreflang для SEO
This commit is contained in:
BIN
ospabhost/frontend/node_modules.zip
Normal file
BIN
ospabhost/frontend/node_modules.zip
Normal file
Binary file not shown.
@@ -37,6 +37,7 @@ const Billing = () => {
|
|||||||
const [balance, setBalance] = useState<number>(0);
|
const [balance, setBalance] = useState<number>(0);
|
||||||
const [payments, setPayments] = useState<CryptoPayment[]>([]);
|
const [payments, setPayments] = useState<CryptoPayment[]>([]);
|
||||||
const [exchangeRate, setExchangeRate] = useState<number>(95);
|
const [exchangeRate, setExchangeRate] = useState<number>(95);
|
||||||
|
const [amount, setAmount] = useState<string>('500'); // Default 500 RUB
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [messageType, setMessageType] = useState<'success' | 'error'>('success');
|
const [messageType, setMessageType] = useState<'success' | 'error'>('success');
|
||||||
|
|
||||||
@@ -151,11 +152,29 @@ const Billing = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const amountInRub = parseFloat(amount);
|
||||||
|
if (isNaN(amountInRub) || amountInRub < 50) {
|
||||||
|
showMessage(
|
||||||
|
isEn ? 'Please enter amount at least 50 RUB' : 'Минимальная сумма 50 ₽',
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amountInUSDT = (amountInRub / exchangeRate).toFixed(2);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Open DePay payment widget
|
// Open DePay payment widget
|
||||||
window.DePayWidgets.Payment({
|
window.DePayWidgets.Payment({
|
||||||
integration: DEPAY_INTEGRATION_ID,
|
integration: DEPAY_INTEGRATION_ID,
|
||||||
|
|
||||||
|
// Amount to pay in USDT
|
||||||
|
amount: {
|
||||||
|
token: 'USDT',
|
||||||
|
blockchain: 'polygon',
|
||||||
|
amount: amountInUSDT,
|
||||||
|
},
|
||||||
|
|
||||||
// User identifier for callback
|
// User identifier for callback
|
||||||
user: {
|
user: {
|
||||||
id: String(userData.user.id),
|
id: String(userData.user.id),
|
||||||
@@ -303,6 +322,35 @@ const Billing = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Форма ввода суммы */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
{isEn ? 'Top-up amount (RUB)' : 'Сумма пополнения (₽)'}
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={amount}
|
||||||
|
onChange={(e) => setAmount(e.target.value)}
|
||||||
|
min="50"
|
||||||
|
step="50"
|
||||||
|
className="w-full px-4 py-3 pr-20 rounded-xl border-2 border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 dark:focus:ring-indigo-800 transition-all text-lg font-semibold"
|
||||||
|
placeholder={isEn ? 'Enter amount' : 'Введите сумму'}
|
||||||
|
/>
|
||||||
|
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-500 dark:text-gray-400 font-semibold">
|
||||||
|
₽
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between mt-2">
|
||||||
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{isEn ? 'Minimum: 50 RUB' : 'Минимум: 50 ₽'}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm font-semibold text-indigo-600 dark:text-indigo-400">
|
||||||
|
≈ {(parseFloat(amount || '0') / exchangeRate).toFixed(4)} USDT
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Кнопка оплаты через DePay */}
|
{/* Кнопка оплаты через DePay */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,12 +1,59 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import { copyFileSync } from 'fs'
|
import { copyFileSync, writeFileSync } from 'fs'
|
||||||
import { dirname, resolve } from 'path'
|
import { dirname, resolve } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
const __dirname = dirname(__filename)
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
|
// Функция для генерации sitemap.xml
|
||||||
|
function generateSitemap() {
|
||||||
|
const baseUrl = 'https://ospab.host';
|
||||||
|
const lastmod = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
|
const pages = [
|
||||||
|
{ loc: '/', priority: '1.0', changefreq: 'weekly' },
|
||||||
|
{ loc: '/about', priority: '0.9', changefreq: 'monthly' },
|
||||||
|
{ loc: '/login', priority: '0.7', changefreq: 'monthly' },
|
||||||
|
{ loc: '/register', priority: '0.8', changefreq: 'monthly' },
|
||||||
|
{ loc: '/blog', priority: '0.85', changefreq: 'daily' },
|
||||||
|
{ loc: '/tariffs', priority: '0.9', changefreq: 'weekly' },
|
||||||
|
{ loc: '/s3plans', priority: '0.9', changefreq: 'weekly' },
|
||||||
|
{ loc: '/terms', priority: '0.5', changefreq: 'yearly' },
|
||||||
|
{ loc: '/privacy', priority: '0.5', changefreq: 'yearly' },
|
||||||
|
];
|
||||||
|
|
||||||
|
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
||||||
|
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">\n';
|
||||||
|
|
||||||
|
for (const page of pages) {
|
||||||
|
// Русская версия (без префикса)
|
||||||
|
xml += ' <url>\n';
|
||||||
|
xml += ` <loc>${baseUrl}${page.loc}</loc>\n`;
|
||||||
|
xml += ` <lastmod>${lastmod}</lastmod>\n`;
|
||||||
|
xml += ` <priority>${page.priority}</priority>\n`;
|
||||||
|
xml += ` <changefreq>${page.changefreq}</changefreq>\n`;
|
||||||
|
xml += ` <xhtml:link rel="alternate" hreflang="ru" href="${baseUrl}${page.loc}"/>\n`;
|
||||||
|
xml += ` <xhtml:link rel="alternate" hreflang="en" href="${baseUrl}/en${page.loc}"/>\n`;
|
||||||
|
xml += ' </url>\n';
|
||||||
|
|
||||||
|
// Английская версия (с префиксом /en)
|
||||||
|
xml += ' <url>\n';
|
||||||
|
xml += ` <loc>${baseUrl}/en${page.loc}</loc>\n`;
|
||||||
|
xml += ` <lastmod>${lastmod}</lastmod>\n`;
|
||||||
|
xml += ` <priority>${page.priority}</priority>\n`;
|
||||||
|
xml += ` <changefreq>${page.changefreq}</changefreq>\n`;
|
||||||
|
xml += ` <xhtml:link rel="alternate" hreflang="ru" href="${baseUrl}${page.loc}"/>\n`;
|
||||||
|
xml += ` <xhtml:link rel="alternate" hreflang="en" href="${baseUrl}/en${page.loc}"/>\n`;
|
||||||
|
xml += ' </url>\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
xml += '</urlset>';
|
||||||
|
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -25,6 +72,23 @@ export default defineConfig({
|
|||||||
console.error('❌ Ошибка копирования service worker:', error)
|
console.error('❌ Ошибка копирования service worker:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'generate-sitemap',
|
||||||
|
writeBundle() {
|
||||||
|
// Генерируем sitemap.xml при сборке
|
||||||
|
try {
|
||||||
|
const sitemapContent = generateSitemap();
|
||||||
|
writeFileSync(
|
||||||
|
resolve(__dirname, 'dist/sitemap.xml'),
|
||||||
|
sitemapContent,
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
console.log('✅ Sitemap.xml сгенерирован в dist/');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка генерации sitemap:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
@@ -168,13 +168,34 @@ server {
|
|||||||
# Robots.txt
|
# Robots.txt
|
||||||
location = /robots.txt {
|
location = /robots.txt {
|
||||||
access_log off;
|
access_log off;
|
||||||
try_files $uri =404;
|
try_files $uri @backend_robots;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Sitemap
|
# Sitemap - try static first, then backend
|
||||||
location = /sitemap.xml {
|
location = /sitemap.xml {
|
||||||
access_log off;
|
access_log off;
|
||||||
try_files $uri =404;
|
add_header Content-Type application/xml;
|
||||||
|
try_files $uri @backend_sitemap;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fallback to backend for robots.txt
|
||||||
|
location @backend_robots {
|
||||||
|
proxy_pass https://backend_api/robots.txt;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fallback to backend for sitemap
|
||||||
|
location @backend_sitemap {
|
||||||
|
proxy_pass https://backend_api/sitemap.xml;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Custom error page handler
|
# Custom error page handler
|
||||||
|
|||||||
Reference in New Issue
Block a user