From 92a986c858456495badb75220659184549892494 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 23 Mar 2026 21:45:02 +0700 Subject: [PATCH] =?UTF-8?q?fix(rates):=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=BD=D1=8B=D0=B9=20=D0=BF=D1=80=D0=BE=D0=BA=D1=81=D0=B8?= =?UTF-8?q?=20/api/rates-proxy=20=D0=B8=20=D0=B1=D0=B5=D0=B9=D0=B4=D0=B6?= =?UTF-8?q?=20=C2=AB=D0=9A=D1=83=D1=80=D1=81=D1=8B=20=D0=BD=D0=B5=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B6=D0=B5=D0=BD=D1=8B=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен /api/rates-proxy для загрузки курсов без CORS - Фронтенд загружает курсы через прокси, а не напрямую - convertWithProviderRate возвращает source=no-rates при отсутствии ratesData - ConvertedAmount показывает «Курсы не загружены» (жёлтый) вместо ложного «Глобальный курс» Made-with: Cursor --- server/index.js | 2 ++ server/routes/rates-proxy.js | 22 ++++++++++++++++++++++ src/App.jsx | 3 ++- src/components/ConvertedAmount.jsx | 8 +++++++- src/lib/utils.js | 10 ++++++++-- 5 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 server/routes/rates-proxy.js diff --git a/server/index.js b/server/index.js index f17e36d..99353af 100644 --- a/server/index.js +++ b/server/index.js @@ -18,6 +18,7 @@ import settingsRouter from './routes/settings.js' import syncRouter from './routes/sync.js' import projectsRouter from './routes/projects.js' import backupRouter from './routes/backup.js' +import ratesProxyRouter from './routes/rates-proxy.js' const app = express() const PORT = process.env.PORT || 3001 @@ -39,6 +40,7 @@ app.use(express.json({ limit: '50mb' })) app.use('/api/sync', syncRouter) app.use('/api/projects', projectsRouter) app.use('/api/backup', backupRouter) + app.use('/api/rates-proxy', ratesProxyRouter) if (existsSync(distPath)) { app.use(express.static(distPath)) diff --git a/server/routes/rates-proxy.js b/server/routes/rates-proxy.js new file mode 100644 index 0000000..ae800ee --- /dev/null +++ b/server/routes/rates-proxy.js @@ -0,0 +1,22 @@ +import { Router } from 'express' + +const router = Router() + +router.get('/', async (req, res) => { + const url = req.query.url + if (!url) { + return res.status(400).json({ error: 'Missing url parameter' }) + } + try { + const response = await fetch(url, { signal: AbortSignal.timeout(10_000) }) + if (!response.ok) { + return res.status(502).json({ error: `Upstream returned ${response.status}` }) + } + const data = await response.json() + res.json(data) + } catch (err) { + res.status(502).json({ error: err.message || 'Failed to fetch rates' }) + } +}) + +export default router diff --git a/src/App.jsx b/src/App.jsx index 7113135..bfb9c2a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -56,7 +56,8 @@ function App() { if (!settings?.ratesUrl) { return } - fetch(settings.ratesUrl) + const proxyUrl = `/api/rates-proxy?url=${encodeURIComponent(settings.ratesUrl)}` + fetch(proxyUrl) .then((response) => { if (!response.ok) { throw new Error('Не удалось получить курсы валют') diff --git a/src/components/ConvertedAmount.jsx b/src/components/ConvertedAmount.jsx index f60c3d5..176ae20 100644 --- a/src/components/ConvertedAmount.jsx +++ b/src/components/ConvertedAmount.jsx @@ -7,12 +7,16 @@ function sourceMeta(source) { if (source === 'global') { return { label: 'Глобальный курс', className: 'bg-blue-lt text-blue' } } + if (source === 'no-rates') { + return { label: 'Курсы не загружены', className: 'bg-yellow-lt text-yellow' } + } return { label: 'Без конвертации', className: 'bg-secondary-lt text-secondary' } } export function ConvertedAmount({ amount, currency, provider, settings, ratesData }) { const result = convertWithProviderRate(amount, currency, provider, settings, ratesData) const meta = sourceMeta(result.source) + const showOriginal = result.source !== 'native' && result.source !== 'no-rates' return (
@@ -20,7 +24,9 @@ export function ConvertedAmount({ amount, currency, provider, settings, ratesDat {formatCurrency(result.value, result.currency)} {meta.label}
-
{formatCurrency(amount, currency)}
+ {showOriginal ? ( +
{formatCurrency(amount, currency)}
+ ) : null} ) } diff --git a/src/lib/utils.js b/src/lib/utils.js index 786fba5..61a3231 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -337,10 +337,16 @@ export function convertWithProviderRate(amount, currency, provider, appSettings, return { value: safeAmount * eurRate, currency: appBase, source: 'provider' } } + if (!ratesData || !ratesData.rates || !ratesData.base) { + return { value: safeAmount, currency: fromCurrency, source: 'no-rates' } + } + + const converted = convertCurrency(safeAmount, fromCurrency, appBase, ratesData) + const didConvert = Math.abs(converted - safeAmount) > 0.0001 || fromCurrency === appBase return { - value: convertCurrency(safeAmount, fromCurrency, appBase, ratesData), + value: converted, currency: appBase, - source: 'global', + source: didConvert ? 'global' : 'no-rates', } }