fix(rates): серверный прокси /api/rates-proxy и бейдж «Курсы не загружены»
- Добавлен /api/rates-proxy для загрузки курсов без CORS - Фронтенд загружает курсы через прокси, а не напрямую - convertWithProviderRate возвращает source=no-rates при отсутствии ratesData - ConvertedAmount показывает «Курсы не загружены» (жёлтый) вместо ложного «Глобальный курс» Made-with: Cursor
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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
|
||||
+2
-1
@@ -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('Не удалось получить курсы валют')
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
@@ -20,7 +24,9 @@ export function ConvertedAmount({ amount, currency, provider, settings, ratesDat
|
||||
<span>{formatCurrency(result.value, result.currency)}</span>
|
||||
<span className={`badge ${meta.className}`}>{meta.label}</span>
|
||||
</div>
|
||||
<div className="text-secondary small">{formatCurrency(amount, currency)}</div>
|
||||
{showOriginal ? (
|
||||
<div className="text-secondary small">{formatCurrency(amount, currency)}</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+8
-2
@@ -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',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user