feat: Add favicon display functionality in BillingManager for improved visual identification of providers
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m41s

This commit is contained in:
2025-08-08 12:03:20 +07:00
parent 560120f5b8
commit ebfc5a9fab
+52 -40
View File
@@ -325,6 +325,30 @@ function BillingManager() {
return String.fromCodePoint(...codePoints);
}
// Favicon для провайдера по ссылке входа
function normalizeToUrl(value) {
if (!value) return '';
try {
const hasProtocol = /^https?:\/\//i.test(value);
const url = new URL(hasProtocol ? value : `https://${value}`);
return url.toString();
} catch {
return '';
}
}
function getFaviconUrl(loginUrl) {
const normalized = normalizeToUrl(loginUrl);
if (!normalized) return '';
try {
const { hostname } = new URL(normalized);
// DuckDuckGo Icons API: быстрая выдача favicon по домену
return `https://icons.duckduckgo.com/ip3/${hostname}.ico`;
} catch {
return '';
}
}
function getCountryFlag(countryRaw) {
if (!countryRaw) return '';
const value = String(countryRaw).trim();
@@ -717,9 +741,19 @@ function BillingManager() {
</div>
</td>
<td>
<span className="badge bg-green-lt text-green">
{item.provider}
</span>
<div className="d-flex align-items-center gap-2">
{item.loginUrl && (
<img
src={getFaviconUrl(item.loginUrl)}
alt=""
width="16"
height="16"
style={{ borderRadius: 3 }}
onError={(e) => { e.currentTarget.style.display = 'none'; }}
/>
)}
<span className="badge bg-green-lt text-green">{item.provider}</span>
</div>
</td>
<td>
<div className="fw-bold">
@@ -880,7 +914,19 @@ function BillingManager() {
<tr key={r._key}>
<td>
<div className="fw-bold">{r.server.hostName}</div>
<div className="text-muted small">{r.server.provider}</div>
<div className="text-muted small d-flex align-items-center gap-2">
{r.server.loginUrl && (
<img
src={getFaviconUrl(r.server.loginUrl)}
alt=""
width="14"
height="14"
style={{ borderRadius: 3 }}
onError={(e) => { e.currentTarget.style.display = 'none'; }}
/>
)}
<span>{r.server.provider}</span>
</div>
</td>
<td>{formatDate(r.date)}</td>
<td>
@@ -1318,42 +1364,8 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
/>
</div>
{/* Исторические платежи */}
<div className="col-12">
<h6 className="text-muted mb-3 mt-4">Исторические платежи</h6>
</div>
<div className="col-md-6">
<label className="form-label">Дата последнего платежа</label>
<input
type="date"
className="form-control"
value={item.lastPaymentDate || ''}
onChange={(e) => onItemChange({ ...item, lastPaymentDate: e.target.value })}
/>
</div>
<div className="col-md-6">
<label className="form-label">Сумма последнего платежа</label>
<input
type="number"
step="0.01"
className="form-control"
value={item.lastPaymentAmount || 0}
onChange={(e) => onItemChange({ ...item, lastPaymentAmount: parseFloat(e.target.value) || 0 })}
/>
</div>
<div className="col-md-6">
<label className="form-label">Валюта последнего платежа</label>
<select
className="form-select"
value={item.lastPaymentCurrency || 'USD'}
onChange={(e) => onItemChange({ ...item, lastPaymentCurrency: e.target.value })}
>
<option value="USD">USD</option>
<option value="EUR">EUR</option>
<option value="RUB">RUB</option>
</select>
</div>
<div className="col-md-6">
{/* Дополнительно */}
<div className="col-md-12">
<label className="form-label">Заметки</label>
<input
type="text"