Фолбэк на кэш при недоступности PocketBase (Pricing/FAQ)
Pricing: при полном отказе всех запросов пробует localStorage-кэш последнего успешного ответа вместо пустого состояния, пишет туда же при успехе. Faq: отличает "в кэше правда пусто" от "не смогли загрузить" — раньше оба случая показывали один и тот же текст "0 protocols found", что вводило в заблуждение при обычном сетевом сбое. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,10 +25,13 @@ export async function FAQ({
|
|||||||
lang: string;
|
lang: string;
|
||||||
}) {
|
}) {
|
||||||
let faqs: PBFaq[] = [];
|
let faqs: PBFaq[] = [];
|
||||||
|
// Различаем "PocketBase ответил, но для этого языка коллекции ещё нет" (штатно,
|
||||||
|
// это и есть fallback на en ниже) от "PocketBase вообще не ответил ни разу" —
|
||||||
|
// раньше оба случая одинаково показывали "0 protocols found", что вводит в
|
||||||
|
// заблуждение при реальном сбое инфраструктуры (выглядит как "у нас нет FAQ",
|
||||||
|
// а не "сейчас не грузится").
|
||||||
|
let hadFetchError = false;
|
||||||
|
|
||||||
// CMS-контент (в отличие от статичных dictionaries/*.json) переведён только
|
|
||||||
// для части языков — коллекции `faqs_<lang>` для новых языков ещё не заведены
|
|
||||||
// в PocketBase. Пока для них нет перевода, показываем английский, а не пустоту.
|
|
||||||
for (const fallbackLang of Array.from(new Set([lang, "en"]))) {
|
for (const fallbackLang of Array.from(new Set([lang, "en"]))) {
|
||||||
try {
|
try {
|
||||||
faqs = await pb.collection(`faqs_${fallbackLang}`).getFullList<PBFaq>({
|
faqs = await pb.collection(`faqs_${fallbackLang}`).getFullList<PBFaq>({
|
||||||
@@ -36,8 +39,10 @@ export async function FAQ({
|
|||||||
fetch: (url, config) =>
|
fetch: (url, config) =>
|
||||||
fetch(url, { ...config, next: { revalidate: 86400 } }), // Кэшируем на сутки
|
fetch(url, { ...config, next: { revalidate: 86400 } }), // Кэшируем на сутки
|
||||||
});
|
});
|
||||||
|
hadFetchError = false;
|
||||||
break;
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
hadFetchError = true;
|
||||||
console.error(`Ошибка загрузки FAQ (${fallbackLang}):`, error);
|
console.error(`Ошибка загрузки FAQ (${fallbackLang}):`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,7 +93,7 @@ export async function FAQ({
|
|||||||
{faqs.length === 0 ? (
|
{faqs.length === 0 ? (
|
||||||
<div className="text-center py-20 border border-dashed border-border/50 rounded-[2.5rem] bg-card/20">
|
<div className="text-center py-20 border border-dashed border-border/50 rounded-[2.5rem] bg-card/20">
|
||||||
<p className="text-muted-foreground font-mono text-sm uppercase tracking-widest">
|
<p className="text-muted-foreground font-mono text-sm uppercase tracking-widest">
|
||||||
0 protocols found in cache.
|
{hadFetchError ? dict.loadError : "0 protocols found in cache."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -63,9 +63,14 @@ export function Pricing({
|
|||||||
const [period, setPeriod] = useState<Period>("1y");
|
const [period, setPeriod] = useState<Period>("1y");
|
||||||
const [plans, setPlans] = useState<PBPricingPlan[]>([]);
|
const [plans, setPlans] = useState<PBPricingPlan[]>([]);
|
||||||
const [loading, setLoading] = useState(!!lang);
|
const [loading, setLoading] = useState(!!lang);
|
||||||
|
// PocketBase недоступен целиком (не просто нет перевода для этого языка) —
|
||||||
|
// показываем последний успешно закэшированный список вместо пустой секции.
|
||||||
|
const [usingCache, setUsingCache] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!lang) return;
|
if (!lang) return;
|
||||||
|
const cacheKey = `netrunner_pricing_cache_${lang}`;
|
||||||
|
|
||||||
async function fetchPlans() {
|
async function fetchPlans() {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -74,6 +79,7 @@ export function Pricing({
|
|||||||
// `pricing_<lang>` для новых языков ещё не заведена в PocketBase.
|
// `pricing_<lang>` для новых языков ещё не заведена в PocketBase.
|
||||||
// Пока для них нет перевода, показываем английские тарифы, а не пустоту.
|
// Пока для них нет перевода, показываем английские тарифы, а не пустоту.
|
||||||
let records: PBPricingPlan[] = [];
|
let records: PBPricingPlan[] = [];
|
||||||
|
let allFailed = true;
|
||||||
for (const fallbackLang of Array.from(new Set([lang, "en"]))) {
|
for (const fallbackLang of Array.from(new Set([lang, "en"]))) {
|
||||||
try {
|
try {
|
||||||
records = await pb
|
records = await pb
|
||||||
@@ -82,11 +88,34 @@ export function Pricing({
|
|||||||
sort: "order",
|
sort: "order",
|
||||||
requestKey: null,
|
requestKey: null,
|
||||||
});
|
});
|
||||||
|
allFailed = false;
|
||||||
break;
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Ошибка загрузки тарифов (${fallbackLang}):`, error);
|
console.error(`Ошибка загрузки тарифов (${fallbackLang}):`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allFailed) {
|
||||||
|
try {
|
||||||
|
const cached = localStorage.getItem(cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
records = JSON.parse(cached);
|
||||||
|
setUsingCache(true);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// localStorage недоступен (приватный режим и т.п.) — просто нет кеша.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setUsingCache(false);
|
||||||
|
if (records.length > 0) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(cacheKey, JSON.stringify(records));
|
||||||
|
} catch {
|
||||||
|
// Диск/квота localStorage — не критично, просто не кешируем.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setPlans(records);
|
setPlans(records);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -121,10 +150,16 @@ export function Pricing({
|
|||||||
>
|
>
|
||||||
{dict.badge}
|
{dict.badge}
|
||||||
</Badge>
|
</Badge>
|
||||||
<h2 className="text-4xl md:text-5xl font-bold tracking-tighter mb-8">
|
<h2 className="text-4xl md:text-5xl font-bold tracking-tighter mb-4">
|
||||||
{dict.title}
|
{dict.title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
|
{usingCache && (
|
||||||
|
<p className="mb-4 text-xs font-mono uppercase tracking-widest text-muted-foreground/70">
|
||||||
|
{dict.offlineNotice}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="inline-flex items-center justify-center p-1.5 bg-card/50 border border-border/50 rounded-2xl backdrop-blur-sm relative">
|
<div className="inline-flex items-center justify-center p-1.5 bg-card/50 border border-border/50 rounded-2xl backdrop-blur-sm relative">
|
||||||
{periods.map((p: PeriodOption) => (
|
{periods.map((p: PeriodOption) => (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -59,11 +59,13 @@
|
|||||||
"billedOnce": "يُدفع دفعة واحدة:",
|
"billedOnce": "يُدفع دفعة واحدة:",
|
||||||
"period1m": "شهر واحد",
|
"period1m": "شهر واحد",
|
||||||
"period6m": "6 أشهر",
|
"period6m": "6 أشهر",
|
||||||
"period1y": "سنة واحدة"
|
"period1y": "سنة واحدة",
|
||||||
|
"offlineNotice": "يتم عرض آخر أسعار محفوظة — لا يتوفر اتصال بالخادم الآن."
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "استعلامات النظام (الأسئلة الشائعة)",
|
"title": "استعلامات النظام (الأسئلة الشائعة)",
|
||||||
"empty": "لم يتم العثور على سجلات."
|
"empty": "لم يتم العثور على سجلات.",
|
||||||
|
"loadError": "فشل التحميل — حاول تحديث الصفحة."
|
||||||
},
|
},
|
||||||
"blog": {
|
"blog": {
|
||||||
"badge": "سجلات البيانات",
|
"badge": "سجلات البيانات",
|
||||||
|
|||||||
@@ -59,11 +59,13 @@
|
|||||||
"billedOnce": "Спішацца адразу:",
|
"billedOnce": "Спішацца адразу:",
|
||||||
"period1m": "1 месяц",
|
"period1m": "1 месяц",
|
||||||
"period6m": "6 месяцаў",
|
"period6m": "6 месяцаў",
|
||||||
"period1y": "1 год"
|
"period1y": "1 год",
|
||||||
|
"offlineNotice": "Паказаны апошнія захаваныя тарыфы — зараз няма сувязі з серверам."
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "Сістэмныя запыты (FAQ)",
|
"title": "Сістэмныя запыты (FAQ)",
|
||||||
"empty": "База ведаў пустая."
|
"empty": "База ведаў пустая.",
|
||||||
|
"loadError": "Не ўдалося загрузіць — паспрабуйце абнавіць старонку."
|
||||||
},
|
},
|
||||||
"blog": {
|
"blog": {
|
||||||
"badge": "Data Logs",
|
"badge": "Data Logs",
|
||||||
|
|||||||
@@ -59,11 +59,13 @@
|
|||||||
"billedOnce": "Billed once:",
|
"billedOnce": "Billed once:",
|
||||||
"period1m": "1 month",
|
"period1m": "1 month",
|
||||||
"period6m": "6 months",
|
"period6m": "6 months",
|
||||||
"period1y": "1 year"
|
"period1y": "1 year",
|
||||||
|
"offlineNotice": "Showing last saved prices — live data unavailable right now."
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "System Queries (FAQ)",
|
"title": "System Queries (FAQ)",
|
||||||
"empty": "No records found."
|
"empty": "No records found.",
|
||||||
|
"loadError": "Failed to load — try refreshing the page."
|
||||||
},
|
},
|
||||||
"blog": {
|
"blog": {
|
||||||
"badge": "Data Logs",
|
"badge": "Data Logs",
|
||||||
|
|||||||
@@ -59,11 +59,13 @@
|
|||||||
"billedOnce": "پرداخت یکباره:",
|
"billedOnce": "پرداخت یکباره:",
|
||||||
"period1m": "۱ ماه",
|
"period1m": "۱ ماه",
|
||||||
"period6m": "۶ ماه",
|
"period6m": "۶ ماه",
|
||||||
"period1y": "۱ سال"
|
"period1y": "۱ سال",
|
||||||
|
"offlineNotice": "قیمتهای ذخیرهشده قبلی نمایش داده میشود — اتصال به سرور اکنون برقرار نیست."
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "پرسشهای سیستم (سوالات متداول)",
|
"title": "پرسشهای سیستم (سوالات متداول)",
|
||||||
"empty": "هیچ رکوردی یافت نشد."
|
"empty": "هیچ رکوردی یافت نشد.",
|
||||||
|
"loadError": "بارگذاری ناموفق بود — صفحه را دوباره بارگذاری کنید."
|
||||||
},
|
},
|
||||||
"blog": {
|
"blog": {
|
||||||
"badge": "لاگهای داده",
|
"badge": "لاگهای داده",
|
||||||
|
|||||||
@@ -59,11 +59,13 @@
|
|||||||
"billedOnce": "Спишется разом:",
|
"billedOnce": "Спишется разом:",
|
||||||
"period1m": "1 месяц",
|
"period1m": "1 месяц",
|
||||||
"period6m": "6 месяцев",
|
"period6m": "6 месяцев",
|
||||||
"period1y": "1 год"
|
"period1y": "1 год",
|
||||||
|
"offlineNotice": "Показаны последние сохранённые тарифы — сейчас нет связи с сервером."
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "Системные запросы (FAQ)",
|
"title": "Системные запросы (FAQ)",
|
||||||
"empty": "База знаний пуста."
|
"empty": "База знаний пуста.",
|
||||||
|
"loadError": "Не удалось загрузить — попробуйте обновить страницу."
|
||||||
},
|
},
|
||||||
"blog": {
|
"blog": {
|
||||||
"badge": "Data Logs",
|
"badge": "Data Logs",
|
||||||
|
|||||||
@@ -59,11 +59,13 @@
|
|||||||
"billedOnce": "Tek seferlik ödeme:",
|
"billedOnce": "Tek seferlik ödeme:",
|
||||||
"period1m": "1 ay",
|
"period1m": "1 ay",
|
||||||
"period6m": "6 ay",
|
"period6m": "6 ay",
|
||||||
"period1y": "1 yıl"
|
"period1y": "1 yıl",
|
||||||
|
"offlineNotice": "Son kaydedilen fiyatlar gösteriliyor — şu anda sunucuya bağlanılamıyor."
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "Sistem Sorguları (SSS)",
|
"title": "Sistem Sorguları (SSS)",
|
||||||
"empty": "Kayıt bulunamadı."
|
"empty": "Kayıt bulunamadı.",
|
||||||
|
"loadError": "Yüklenemedi — sayfayı yenilemeyi deneyin."
|
||||||
},
|
},
|
||||||
"blog": {
|
"blog": {
|
||||||
"badge": "Veri Kayıtları",
|
"badge": "Veri Kayıtları",
|
||||||
|
|||||||
@@ -59,11 +59,13 @@
|
|||||||
"billedOnce": "Спишеться одразу:",
|
"billedOnce": "Спишеться одразу:",
|
||||||
"period1m": "1 місяць",
|
"period1m": "1 місяць",
|
||||||
"period6m": "6 місяців",
|
"period6m": "6 місяців",
|
||||||
"period1y": "1 рік"
|
"period1y": "1 рік",
|
||||||
|
"offlineNotice": "Показано останні збережені тарифи — зараз немає зв'язку із сервером."
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "Системні запити (FAQ)",
|
"title": "Системні запити (FAQ)",
|
||||||
"empty": "База знань порожня."
|
"empty": "База знань порожня.",
|
||||||
|
"loadError": "Не вдалося завантажити — спробуйте оновити сторінку."
|
||||||
},
|
},
|
||||||
"blog": {
|
"blog": {
|
||||||
"badge": "Data Logs",
|
"badge": "Data Logs",
|
||||||
|
|||||||
@@ -59,11 +59,13 @@
|
|||||||
"billedOnce": "Bir martalik to'lov:",
|
"billedOnce": "Bir martalik to'lov:",
|
||||||
"period1m": "1 oy",
|
"period1m": "1 oy",
|
||||||
"period6m": "6 oy",
|
"period6m": "6 oy",
|
||||||
"period1y": "1 yil"
|
"period1y": "1 yil",
|
||||||
|
"offlineNotice": "Oxirgi saqlangan narxlar ko'rsatilmoqda — hozir serverga ulanib bo'lmayapti."
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "Tizim So'rovlari (Savollar)",
|
"title": "Tizim So'rovlari (Savollar)",
|
||||||
"empty": "Yozuvlar topilmadi."
|
"empty": "Yozuvlar topilmadi.",
|
||||||
|
"loadError": "Yuklab bo'lmadi — sahifani yangilashga urinib ko'ring."
|
||||||
},
|
},
|
||||||
"blog": {
|
"blog": {
|
||||||
"badge": "Ma'lumotlar Jurnali",
|
"badge": "Ma'lumotlar Jurnali",
|
||||||
|
|||||||
@@ -59,11 +59,13 @@
|
|||||||
"billedOnce": "Thanh toán một lần:",
|
"billedOnce": "Thanh toán một lần:",
|
||||||
"period1m": "1 tháng",
|
"period1m": "1 tháng",
|
||||||
"period6m": "6 tháng",
|
"period6m": "6 tháng",
|
||||||
"period1y": "1 năm"
|
"period1y": "1 năm",
|
||||||
|
"offlineNotice": "Đang hiển thị giá đã lưu gần nhất — hiện không thể kết nối máy chủ."
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "Truy Vấn Hệ Thống (Hỏi Đáp)",
|
"title": "Truy Vấn Hệ Thống (Hỏi Đáp)",
|
||||||
"empty": "Không tìm thấy bản ghi nào."
|
"empty": "Không tìm thấy bản ghi nào.",
|
||||||
|
"loadError": "Không tải được — hãy thử tải lại trang."
|
||||||
},
|
},
|
||||||
"blog": {
|
"blog": {
|
||||||
"badge": "Nhật Ký Dữ Liệu",
|
"badge": "Nhật Ký Dữ Liệu",
|
||||||
|
|||||||
@@ -59,11 +59,13 @@
|
|||||||
"billedOnce": "一次性扣款:",
|
"billedOnce": "一次性扣款:",
|
||||||
"period1m": "1 个月",
|
"period1m": "1 个月",
|
||||||
"period6m": "6 个月",
|
"period6m": "6 个月",
|
||||||
"period1y": "1 年"
|
"period1y": "1 年",
|
||||||
|
"offlineNotice": "显示上次保存的价格——当前无法连接服务器。"
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "系统查询(常见问题)",
|
"title": "系统查询(常见问题)",
|
||||||
"empty": "未找到记录。"
|
"empty": "未找到记录。",
|
||||||
|
"loadError": "加载失败——请尝试刷新页面。"
|
||||||
},
|
},
|
||||||
"blog": {
|
"blog": {
|
||||||
"badge": "数据日志",
|
"badge": "数据日志",
|
||||||
|
|||||||
@@ -42,10 +42,12 @@ export interface Dictionary {
|
|||||||
period1m: string;
|
period1m: string;
|
||||||
period6m: string;
|
period6m: string;
|
||||||
period1y: string;
|
period1y: string;
|
||||||
|
offlineNotice: string;
|
||||||
};
|
};
|
||||||
faq: {
|
faq: {
|
||||||
title: string;
|
title: string;
|
||||||
empty: string;
|
empty: string;
|
||||||
|
loadError: string;
|
||||||
};
|
};
|
||||||
blog: {
|
blog: {
|
||||||
badge: string;
|
badge: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user