feat: 分站前端默认使用人民币显示价格和余额
- 后端 GetDistSiteInfo 返回 currency 配置(符号/汇率/代码) - SiteContext 新增 useCurrency() hook 提供 symbol/rate/fmt - Pricing/Dashboard/Topup/Packages 所有价格从 $ 改为 ¥ 并乘以汇率 - 7 个主题的 Home.jsx 套餐价格同步更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
31e4bd22e4
commit
c9c711e4f6
12 files changed
+72
-36
No files matched your search
@@ -41,3 +41,24 @@ export function useSite() {
|
|||||||
if (!ctx) throw new Error('useSite must be used within SiteProvider');
|
if (!ctx) throw new Error('useSite must be used within SiteProvider');
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useCurrency - 获取分站货币配置
|
||||||
|
* 返回 { symbol, rate, code, fmt(usdValue) }
|
||||||
|
* fmt() 将 USD 值转换为显示货币并格式化
|
||||||
|
*/
|
||||||
|
export function useCurrency() {
|
||||||
|
const { site } = useSite();
|
||||||
|
const currency = site?.currency;
|
||||||
|
const symbol = currency?.symbol || '¥';
|
||||||
|
const rate = currency?.exchange_rate || 7;
|
||||||
|
const code = currency?.code || 'CNY';
|
||||||
|
|
||||||
|
const fmt = (usdValue, decimals = 4) => {
|
||||||
|
if (usdValue == null || isNaN(usdValue)) return '-';
|
||||||
|
const converted = Number(usdValue) * rate;
|
||||||
|
return symbol + converted.toFixed(decimals);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { symbol, rate, code, fmt };
|
||||||
|
}
|
||||||
@@ -3,12 +3,14 @@ import { Link } from 'react-router-dom';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import { getUserUsage, redeemCode, Q, quotaToDollar } from '../api';
|
import { getUserUsage, redeemCode, Q, quotaToDollar } from '../api';
|
||||||
|
import { useCurrency } from '../context/SiteContext';
|
||||||
import CountUp from '../components/bits/CountUp';
|
import CountUp from '../components/bits/CountUp';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user, refreshUser } = useAuth();
|
const { user, refreshUser } = useAuth();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
const [usage, setUsage] = useState(null);
|
const [usage, setUsage] = useState(null);
|
||||||
const [redeemInput, setRedeemInput] = useState('');
|
const [redeemInput, setRedeemInput] = useState('');
|
||||||
const [redeeming, setRedeeming] = useState(false);
|
const [redeeming, setRedeeming] = useState(false);
|
||||||
@@ -40,7 +42,7 @@ export default function Dashboard() {
|
|||||||
const quota = usage?.quota ?? user?.quota ?? 0;
|
const quota = usage?.quota ?? user?.quota ?? 0;
|
||||||
const usedQuota = usage?.used_quota ?? user?.used_quota ?? 0;
|
const usedQuota = usage?.used_quota ?? user?.used_quota ?? 0;
|
||||||
const requestCount = usage?.request_count ?? user?.request_count ?? 0;
|
const requestCount = usage?.request_count ?? user?.request_count ?? 0;
|
||||||
const balanceDollars = quota / Q;
|
const balanceDollars = quota / Q * rate;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-7xl mx-auto px-6 py-10">
|
<div className="max-w-7xl mx-auto px-6 py-10">
|
||||||
@@ -57,7 +59,7 @@ export default function Dashboard() {
|
|||||||
<div className="glass rounded-2xl p-6">
|
<div className="glass rounded-2xl p-6">
|
||||||
<p className="text-sm text-page-secondary mb-2">{t('dashboard.balance')}</p>
|
<p className="text-sm text-page-secondary mb-2">{t('dashboard.balance')}</p>
|
||||||
<div className="text-3xl font-bold text-page">
|
<div className="text-3xl font-bold text-page">
|
||||||
$<CountUp from={0} to={balanceDollars} duration={1.5} />
|
{symbol}<CountUp from={0} to={balanceDollars} duration={1.5} />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: quota.toLocaleString() })}</p>
|
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: quota.toLocaleString() })}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,7 +67,7 @@ export default function Dashboard() {
|
|||||||
<div className="glass rounded-2xl p-6">
|
<div className="glass rounded-2xl p-6">
|
||||||
<p className="text-sm text-page-secondary mb-2">{t('dashboard.used')}</p>
|
<p className="text-sm text-page-secondary mb-2">{t('dashboard.used')}</p>
|
||||||
<div className="text-3xl font-bold text-page">
|
<div className="text-3xl font-bold text-page">
|
||||||
$<CountUp from={0} to={usedQuota / Q} duration={1.5} />
|
{symbol}<CountUp from={0} to={usedQuota / Q * rate} duration={1.5} />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: usedQuota.toLocaleString() })}</p>
|
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: usedQuota.toLocaleString() })}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Link, useNavigate } from 'react-router-dom';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import { getSitePackages, getSiteModels, subscribePackage, getActiveSubscriptions, Q } from '../api';
|
import { getSitePackages, getSiteModels, subscribePackage, getActiveSubscriptions, Q } from '../api';
|
||||||
|
import { useCurrency } from '../context/SiteContext';
|
||||||
import SpotlightCard from '../components/bits/SpotlightCard';
|
import SpotlightCard from '../components/bits/SpotlightCard';
|
||||||
import { calcOfficialEquivList } from '../utils/officialEquiv';
|
import { calcOfficialEquivList } from '../utils/officialEquiv';
|
||||||
import RotatingEquiv from '../components/bits/RotatingEquiv';
|
import RotatingEquiv from '../components/bits/RotatingEquiv';
|
||||||
@@ -24,6 +25,7 @@ export default function Packages() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user, refreshUser } = useAuth();
|
const { user, refreshUser } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
const [packages, setPackages] = useState([]);
|
const [packages, setPackages] = useState([]);
|
||||||
const [models, setModels] = useState([]);
|
const [models, setModels] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -144,7 +146,7 @@ export default function Packages() {
|
|||||||
style={{ width: `${pct}%` }} />
|
style={{ width: `${pct}%` }} />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-page-secondary whitespace-nowrap">
|
<span className="text-xs text-page-secondary whitespace-nowrap">
|
||||||
${(remain / Q).toFixed(2)} / ${(total / Q).toFixed(2)}
|
{symbol}{(remain / Q * rate).toFixed(2)} / {symbol}{(total / Q * rate).toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,9 +206,9 @@ export default function Packages() {
|
|||||||
{/* Price */}
|
{/* Price */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="flex items-baseline gap-2">
|
<div className="flex items-baseline gap-2">
|
||||||
<span className="text-4xl font-bold text-page">${Number(pkg.price).toFixed(2)}</span>
|
<span className="text-4xl font-bold text-page">{symbol}{(Number(pkg.price) * rate).toFixed(2)}</span>
|
||||||
{pkg.original_price > 0 && pkg.original_price > pkg.price && (
|
{pkg.original_price > 0 && pkg.original_price > pkg.price && (
|
||||||
<span className="text-lg text-page-muted line-through">${Number(pkg.original_price).toFixed(2)}</span>
|
<span className="text-lg text-page-muted line-through">{symbol}{(Number(pkg.original_price) * rate).toFixed(2)}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{pkg.duration > 0 && (
|
{pkg.duration > 0 && (
|
||||||
@@ -277,7 +279,7 @@ export default function Packages() {
|
|||||||
|
|
||||||
{/* Confirmation Modal */}
|
{/* Confirmation Modal */}
|
||||||
{confirmPkg && (() => {
|
{confirmPkg && (() => {
|
||||||
const userBalance = (user?.quota || 0) / Q;
|
const userBalance = (user?.quota || 0) / Q * rate;
|
||||||
const pkgPrice = Number(confirmPkg.price);
|
const pkgPrice = Number(confirmPkg.price);
|
||||||
const insufficient = userBalance < pkgPrice;
|
const insufficient = userBalance < pkgPrice;
|
||||||
const resetPeriod = confirmPkg.quota_reset_period || 'never';
|
const resetPeriod = confirmPkg.quota_reset_period || 'never';
|
||||||
@@ -301,7 +303,7 @@ export default function Packages() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="text-sm text-page-secondary mb-4">
|
<p className="text-sm text-page-secondary mb-4">
|
||||||
{t('packages.yourBalance')} <span className={`font-medium ${insufficient ? 'text-page-danger' : 'text-page-success'}`}>${userBalance.toFixed(2)}</span>
|
{t('packages.yourBalance')} <span className={`font-medium ${insufficient ? 'text-page-danger' : 'text-page-success'}`}>{symbol}{userBalance.toFixed(2)}</span>
|
||||||
</p>
|
</p>
|
||||||
{insufficient && (
|
{insufficient && (
|
||||||
<div className="bg-red-500/10 border border-red-500/20 rounded-xl p-3 mb-4">
|
<div className="bg-red-500/10 border border-red-500/20 rounded-xl p-3 mb-4">
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import React, { useState, useEffect, useMemo } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { getSiteModels } from '../api';
|
import { getSiteModels } from '../api';
|
||||||
|
import { useCurrency } from '../context/SiteContext';
|
||||||
|
|
||||||
export default function Pricing() {
|
export default function Pricing() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
const [models, setModels] = useState([]);
|
const [models, setModels] = useState([]);
|
||||||
const [vendors, setVendors] = useState([]);
|
const [vendors, setVendors] = useState([]);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
@@ -132,10 +134,10 @@ export default function Pricing() {
|
|||||||
<span className="font-mono text-page">{m.display_name || m.model_name}</span>
|
<span className="font-mono text-page">{m.display_name || m.model_name}</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-5 py-3.5 text-right font-mono text-page-label">
|
<td className="px-5 py-3.5 text-right font-mono text-page-label">
|
||||||
{m.input_price != null ? `$${(Number(m.input_price) * 1000).toFixed(4)}` : '-'}
|
{m.input_price != null ? `${symbol}${(Number(m.input_price) * 1000 * rate).toFixed(4)}` : '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-5 py-3.5 text-right font-mono text-page-label">
|
<td className="px-5 py-3.5 text-right font-mono text-page-label">
|
||||||
{m.output_price != null ? `$${(Number(m.output_price) * 1000).toFixed(4)}` : '-'}
|
{m.output_price != null ? `${symbol}${(Number(m.output_price) * 1000 * rate).toFixed(4)}` : '-'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-5 py-3.5 text-center">
|
<td className="px-5 py-3.5 text-center">
|
||||||
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs border ${
|
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs border ${
|
||||||
|
|||||||
+7
-5
@@ -8,6 +8,7 @@ import {
|
|||||||
createCryptoOrder, getCryptoOrderStatus, getTopupHistory,
|
createCryptoOrder, getCryptoOrderStatus, getTopupHistory,
|
||||||
Q, quotaToDollar,
|
Q, quotaToDollar,
|
||||||
} from '../api';
|
} from '../api';
|
||||||
|
import { useCurrency } from '../context/SiteContext';
|
||||||
import CountUp from '../components/bits/CountUp';
|
import CountUp from '../components/bits/CountUp';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ export default function Topup() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user, refreshUser } = useAuth();
|
const { user, refreshUser } = useAuth();
|
||||||
const { site } = useSite();
|
const { site } = useSite();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
|
|
||||||
const [usage, setUsage] = useState(null);
|
const [usage, setUsage] = useState(null);
|
||||||
const [topupInfo, setTopupInfo] = useState(null);
|
const [topupInfo, setTopupInfo] = useState(null);
|
||||||
@@ -67,7 +69,7 @@ export default function Topup() {
|
|||||||
const quota = usage?.quota ?? user?.quota ?? 0;
|
const quota = usage?.quota ?? user?.quota ?? 0;
|
||||||
const usedQuota = usage?.used_quota ?? user?.used_quota ?? 0;
|
const usedQuota = usage?.used_quota ?? user?.used_quota ?? 0;
|
||||||
const requestCount = usage?.request_count ?? user?.request_count ?? 0;
|
const requestCount = usage?.request_count ?? user?.request_count ?? 0;
|
||||||
const balanceDollars = quota / Q;
|
const balanceDollars = quota / Q * rate;
|
||||||
|
|
||||||
// Redeem
|
// Redeem
|
||||||
const handleRedeem = async (e) => {
|
const handleRedeem = async (e) => {
|
||||||
@@ -324,7 +326,7 @@ export default function Topup() {
|
|||||||
<div className="glass rounded-2xl p-6">
|
<div className="glass rounded-2xl p-6">
|
||||||
<p className="text-sm text-page-secondary mb-2">{t('dashboard.balance')}</p>
|
<p className="text-sm text-page-secondary mb-2">{t('dashboard.balance')}</p>
|
||||||
<div className="text-3xl font-bold text-page">
|
<div className="text-3xl font-bold text-page">
|
||||||
$<CountUp from={0} to={balanceDollars} duration={1.5} />
|
{symbol}<CountUp from={0} to={balanceDollars} duration={1.5} />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: quota.toLocaleString() })}</p>
|
<p className="text-xs text-page-muted mt-1">{t('dashboard.quotaUnits', { count: quota.toLocaleString() })}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -332,7 +334,7 @@ export default function Topup() {
|
|||||||
<div className="glass rounded-2xl p-6">
|
<div className="glass rounded-2xl p-6">
|
||||||
<p className="text-sm text-page-secondary mb-2">{t('dashboard.used')}</p>
|
<p className="text-sm text-page-secondary mb-2">{t('dashboard.used')}</p>
|
||||||
<div className="text-3xl font-bold text-page">
|
<div className="text-3xl font-bold text-page">
|
||||||
$<CountUp from={0} to={usedQuota / Q} duration={1.5} />
|
{symbol}<CountUp from={0} to={usedQuota / Q * rate} duration={1.5} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -371,7 +373,7 @@ export default function Topup() {
|
|||||||
: 'glass-sm text-page-label hover:text-page hover:bg-page-surface-hover'
|
: 'glass-sm text-page-label hover:text-page hover:bg-page-surface-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
${val}
|
{symbol}{val}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -591,7 +593,7 @@ export default function Topup() {
|
|||||||
{history.map((item, i) => (
|
{history.map((item, i) => (
|
||||||
<div key={i} className="flex items-center justify-between glass-sm rounded-xl px-4 py-3">
|
<div key={i} className="flex items-center justify-between glass-sm rounded-xl px-4 py-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-page">${item.amount}</p>
|
<p className="text-sm text-page">{symbol}{(Number(item.amount) * rate).toFixed(2)}</p>
|
||||||
<p className="text-xs text-page-muted">
|
<p className="text-xs text-page-muted">
|
||||||
{new Date(item.create_time * 1000).toLocaleString()} · {item.payment_method || t('topup.redeemCode')}
|
{new Date(item.create_time * 1000).toLocaleString()} · {item.payment_method || t('topup.redeemCode')}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useSite } from '../../context/SiteContext';
|
import { useSite, useCurrency } from '../../context/SiteContext';
|
||||||
import { getSiteModels, getSitePackages, Q } from '../../api';
|
import { getSiteModels, getSitePackages, Q } from '../../api';
|
||||||
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
||||||
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
||||||
@@ -13,6 +13,7 @@ export default function ClaudeHome() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { site } = useSite();
|
const { site } = useSite();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
const [models, setModels] = useState([]);
|
const [models, setModels] = useState([]);
|
||||||
const [packages, setPackages] = useState([]);
|
const [packages, setPackages] = useState([]);
|
||||||
|
|
||||||
@@ -159,9 +160,9 @@ export default function ClaudeHome() {
|
|||||||
<h3 className="text-base font-semibold text-[#3D3024]">{pkg.name}</h3>
|
<h3 className="text-base font-semibold text-[#3D3024]">{pkg.name}</h3>
|
||||||
{pkg.description && <p className="text-sm text-[#8B7D6E] mt-1">{pkg.description}</p>}
|
{pkg.description && <p className="text-sm text-[#8B7D6E] mt-1">{pkg.description}</p>}
|
||||||
<div className="mt-auto pt-6">
|
<div className="mt-auto pt-6">
|
||||||
<span className="text-3xl font-bold text-[#3D3024]">${pkg.price}</span>
|
<span className="text-3xl font-bold text-[#3D3024]">{symbol}{(Number(pkg.price) * rate).toFixed(2)}</span>
|
||||||
{pkg.original_price > pkg.price && (
|
{pkg.original_price > pkg.price && (
|
||||||
<span className="text-sm text-[#8B7D6E] line-through ml-2">${pkg.original_price}</span>
|
<span className="text-sm text-[#8B7D6E] line-through ml-2">{symbol}{(Number(pkg.original_price) * rate).toFixed(2)}</span>
|
||||||
)}
|
)}
|
||||||
{pkg.duration > 0 && <p className="text-xs text-[#8B7D6E] mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
{pkg.duration > 0 && <p className="text-xs text-[#8B7D6E] mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useSite } from '../../context/SiteContext';
|
import { useSite, useCurrency } from '../../context/SiteContext';
|
||||||
import { getSiteModels, getSitePackages, Q } from '../../api';
|
import { getSiteModels, getSitePackages, Q } from '../../api';
|
||||||
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
||||||
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
||||||
@@ -13,6 +13,7 @@ export default function CleanHome() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { site } = useSite();
|
const { site } = useSite();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
const [models, setModels] = useState([]);
|
const [models, setModels] = useState([]);
|
||||||
const [packages, setPackages] = useState([]);
|
const [packages, setPackages] = useState([]);
|
||||||
|
|
||||||
@@ -172,9 +173,9 @@ export default function CleanHome() {
|
|||||||
<h3 className="text-base font-semibold text-gray-900">{pkg.name}</h3>
|
<h3 className="text-base font-semibold text-gray-900">{pkg.name}</h3>
|
||||||
{pkg.description && <p className="text-sm text-gray-500 mt-1">{pkg.description}</p>}
|
{pkg.description && <p className="text-sm text-gray-500 mt-1">{pkg.description}</p>}
|
||||||
<div className="mt-auto pt-6">
|
<div className="mt-auto pt-6">
|
||||||
<span className="text-3xl font-bold text-gray-900">${pkg.price}</span>
|
<span className="text-3xl font-bold text-gray-900">{symbol}{(Number(pkg.price) * rate).toFixed(2)}</span>
|
||||||
{pkg.original_price > pkg.price && (
|
{pkg.original_price > pkg.price && (
|
||||||
<span className="text-sm text-gray-500 line-through ml-2">${pkg.original_price}</span>
|
<span className="text-sm text-gray-500 line-through ml-2">{symbol}{(Number(pkg.original_price) * rate).toFixed(2)}</span>
|
||||||
)}
|
)}
|
||||||
{pkg.duration > 0 && <p className="text-xs text-gray-500 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
{pkg.duration > 0 && <p className="text-xs text-gray-500 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useSite } from '../../context/SiteContext';
|
import { useSite, useCurrency } from '../../context/SiteContext';
|
||||||
import { getSiteModels, getSitePackages, Q } from '../../api';
|
import { getSiteModels, getSitePackages, Q } from '../../api';
|
||||||
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
||||||
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
||||||
@@ -13,6 +13,7 @@ export default function CorporateHome() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { site } = useSite();
|
const { site } = useSite();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
const [models, setModels] = useState([]);
|
const [models, setModels] = useState([]);
|
||||||
const [packages, setPackages] = useState([]);
|
const [packages, setPackages] = useState([]);
|
||||||
|
|
||||||
@@ -171,9 +172,9 @@ export default function CorporateHome() {
|
|||||||
<h3 className="text-base font-semibold text-slate-900">{pkg.name}</h3>
|
<h3 className="text-base font-semibold text-slate-900">{pkg.name}</h3>
|
||||||
{pkg.description && <p className="text-sm text-slate-500 mt-1">{pkg.description}</p>}
|
{pkg.description && <p className="text-sm text-slate-500 mt-1">{pkg.description}</p>}
|
||||||
<div className="mt-auto pt-6">
|
<div className="mt-auto pt-6">
|
||||||
<span className="text-3xl font-bold text-slate-900">${pkg.price}</span>
|
<span className="text-3xl font-bold text-slate-900">{symbol}{(Number(pkg.price) * rate).toFixed(2)}</span>
|
||||||
{pkg.original_price > pkg.price && (
|
{pkg.original_price > pkg.price && (
|
||||||
<span className="text-sm text-slate-500 line-through ml-2">${pkg.original_price}</span>
|
<span className="text-sm text-slate-500 line-through ml-2">{symbol}{(Number(pkg.original_price) * rate).toFixed(2)}</span>
|
||||||
)}
|
)}
|
||||||
{pkg.duration > 0 && <p className="text-xs text-slate-500 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
{pkg.duration > 0 && <p className="text-xs text-slate-500 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useSite } from '../../context/SiteContext';
|
import { useSite, useCurrency } from '../../context/SiteContext';
|
||||||
import { getSiteModels, getSitePackages, Q } from '../../api';
|
import { getSiteModels, getSitePackages, Q } from '../../api';
|
||||||
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
||||||
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
||||||
@@ -13,6 +13,7 @@ export default function DarkHome() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { site } = useSite();
|
const { site } = useSite();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
const [models, setModels] = useState([]);
|
const [models, setModels] = useState([]);
|
||||||
const [packages, setPackages] = useState([]);
|
const [packages, setPackages] = useState([]);
|
||||||
|
|
||||||
@@ -162,9 +163,9 @@ export default function DarkHome() {
|
|||||||
<h3 className="text-base font-semibold text-white">{pkg.name}</h3>
|
<h3 className="text-base font-semibold text-white">{pkg.name}</h3>
|
||||||
{pkg.description && <p className="text-sm text-neutral-500 mt-1">{pkg.description}</p>}
|
{pkg.description && <p className="text-sm text-neutral-500 mt-1">{pkg.description}</p>}
|
||||||
<div className="mt-auto pt-6">
|
<div className="mt-auto pt-6">
|
||||||
<span className="text-3xl font-bold text-white">${pkg.price}</span>
|
<span className="text-3xl font-bold text-white">{symbol}{(Number(pkg.price) * rate).toFixed(2)}</span>
|
||||||
{pkg.original_price > pkg.price && (
|
{pkg.original_price > pkg.price && (
|
||||||
<span className="text-sm text-neutral-600 line-through ml-2">${pkg.original_price}</span>
|
<span className="text-sm text-neutral-600 line-through ml-2">{symbol}{(Number(pkg.original_price) * rate).toFixed(2)}</span>
|
||||||
)}
|
)}
|
||||||
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useSite } from '../../context/SiteContext';
|
import { useSite, useCurrency } from '../../context/SiteContext';
|
||||||
import { getSiteModels, getSitePackages, Q } from '../../api';
|
import { getSiteModels, getSitePackages, Q } from '../../api';
|
||||||
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
||||||
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
||||||
@@ -13,6 +13,7 @@ export default function DefaultHome() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { site } = useSite();
|
const { site } = useSite();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
const [models, setModels] = useState([]);
|
const [models, setModels] = useState([]);
|
||||||
const [packages, setPackages] = useState([]);
|
const [packages, setPackages] = useState([]);
|
||||||
|
|
||||||
@@ -171,9 +172,9 @@ export default function DefaultHome() {
|
|||||||
<h3 className="text-base font-semibold text-white">{pkg.name}</h3>
|
<h3 className="text-base font-semibold text-white">{pkg.name}</h3>
|
||||||
{pkg.description && <p className="text-sm text-neutral-500 mt-1">{pkg.description}</p>}
|
{pkg.description && <p className="text-sm text-neutral-500 mt-1">{pkg.description}</p>}
|
||||||
<div className="mt-auto pt-6">
|
<div className="mt-auto pt-6">
|
||||||
<span className="text-3xl font-bold text-white">${pkg.price}</span>
|
<span className="text-3xl font-bold text-white">{symbol}{(Number(pkg.price) * rate).toFixed(2)}</span>
|
||||||
{pkg.original_price > pkg.price && (
|
{pkg.original_price > pkg.price && (
|
||||||
<span className="text-sm text-neutral-600 line-through ml-2">${pkg.original_price}</span>
|
<span className="text-sm text-neutral-600 line-through ml-2">{symbol}{(Number(pkg.original_price) * rate).toFixed(2)}</span>
|
||||||
)}
|
)}
|
||||||
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useSite } from '../../context/SiteContext';
|
import { useSite, useCurrency } from '../../context/SiteContext';
|
||||||
import { getSiteModels, getSitePackages, Q } from '../../api';
|
import { getSiteModels, getSitePackages, Q } from '../../api';
|
||||||
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
||||||
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
||||||
@@ -13,6 +13,7 @@ export default function MinimalHome() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { site } = useSite();
|
const { site } = useSite();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
const [models, setModels] = useState([]);
|
const [models, setModels] = useState([]);
|
||||||
const [packages, setPackages] = useState([]);
|
const [packages, setPackages] = useState([]);
|
||||||
|
|
||||||
@@ -154,9 +155,9 @@ export default function MinimalHome() {
|
|||||||
<h3 className="text-base font-semibold text-white">{pkg.name}</h3>
|
<h3 className="text-base font-semibold text-white">{pkg.name}</h3>
|
||||||
{pkg.description && <p className="text-sm text-neutral-500 mt-1">{pkg.description}</p>}
|
{pkg.description && <p className="text-sm text-neutral-500 mt-1">{pkg.description}</p>}
|
||||||
<div className="mt-auto pt-6">
|
<div className="mt-auto pt-6">
|
||||||
<span className="text-3xl font-bold text-white">${pkg.price}</span>
|
<span className="text-3xl font-bold text-white">{symbol}{(Number(pkg.price) * rate).toFixed(2)}</span>
|
||||||
{pkg.original_price > pkg.price && (
|
{pkg.original_price > pkg.price && (
|
||||||
<span className="text-sm text-neutral-600 line-through ml-2">${pkg.original_price}</span>
|
<span className="text-sm text-neutral-600 line-through ml-2">{symbol}{(Number(pkg.original_price) * rate).toFixed(2)}</span>
|
||||||
)}
|
)}
|
||||||
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useSite } from '../../context/SiteContext';
|
import { useSite, useCurrency } from '../../context/SiteContext';
|
||||||
import { getSiteModels, getSitePackages, Q } from '../../api';
|
import { getSiteModels, getSitePackages, Q } from '../../api';
|
||||||
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
import { calcOfficialEquivList } from '../../utils/officialEquiv';
|
||||||
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
import RotatingEquiv from '../../components/bits/RotatingEquiv';
|
||||||
@@ -13,6 +13,7 @@ export default function StarterHome() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { site } = useSite();
|
const { site } = useSite();
|
||||||
|
const { symbol, rate } = useCurrency();
|
||||||
const [models, setModels] = useState([]);
|
const [models, setModels] = useState([]);
|
||||||
const [packages, setPackages] = useState([]);
|
const [packages, setPackages] = useState([]);
|
||||||
|
|
||||||
@@ -177,9 +178,9 @@ export default function StarterHome() {
|
|||||||
<h3 className="text-base font-semibold text-white">{pkg.name}</h3>
|
<h3 className="text-base font-semibold text-white">{pkg.name}</h3>
|
||||||
{pkg.description && <p className="text-sm text-neutral-500 mt-1">{pkg.description}</p>}
|
{pkg.description && <p className="text-sm text-neutral-500 mt-1">{pkg.description}</p>}
|
||||||
<div className="mt-auto pt-6">
|
<div className="mt-auto pt-6">
|
||||||
<span className="text-3xl font-bold text-white">${pkg.price}</span>
|
<span className="text-3xl font-bold text-white">{symbol}{(Number(pkg.price) * rate).toFixed(2)}</span>
|
||||||
{pkg.original_price > pkg.price && (
|
{pkg.original_price > pkg.price && (
|
||||||
<span className="text-sm text-neutral-600 line-through ml-2">${pkg.original_price}</span>
|
<span className="text-sm text-neutral-600 line-through ml-2">{symbol}{(Number(pkg.original_price) * rate).toFixed(2)}</span>
|
||||||
)}
|
)}
|
||||||
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
{pkg.duration > 0 && <p className="text-xs text-neutral-600 mt-1">{t('home.days', { count: pkg.duration })}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user