feat: add subscription management for tauri platforms (#1499)

This commit is contained in:
Huang Xin
2025-06-30 16:04:57 +08:00
committed by GitHub
parent bc36da6b25
commit 5d61a9808f
35 changed files with 326 additions and 160 deletions
+2 -1
View File
@@ -43,10 +43,11 @@
"@ducanh2912/next-pwa": "^10.2.9",
"@fabianlars/tauri-plugin-oauth": "2",
"@opennextjs/cloudflare": "^1.3.1",
"@stripe/react-stripe-js": "^3.7.0",
"@stripe/stripe-js": "^7.4.0",
"@supabase/auth-ui-react": "^0.4.7",
"@supabase/auth-ui-shared": "^0.1.8",
"@supabase/supabase-js": "^2.49.10",
"@supabase/supabase-js": "^2.50.2",
"@tauri-apps/api": "2.6.0",
"@tauri-apps/plugin-cli": "^2.3.0",
"@tauri-apps/plugin-deep-link": "^2.3.0",
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "فشل تنزيل الكتاب: {{title}}",
"Upload Book": "تحميل الكتاب",
"Auto Upload Books to Cloud": "تحميل الكتب تلقائيًا إلى السحابة",
"{{percentage}}% of Cloud Storage Used.": "تم استخدام {{percentage}}% من مساحة التخزين السحابية.",
"Storage": "التخزين",
"Book deleted: {{title}}": "تم حذف الكتاب: {{title}}",
"Failed to delete book: {{title}}": "فشل حذف الكتاب: {{title}}",
"Check Updates on Start": "التحقق من التحديثات عند البدء",
@@ -406,7 +404,6 @@
"Unlimited Text-to-Speech Hours": "ساعات تحويل النص إلى كلام غير محدودة",
"DeepL Free Access (10K characters/day)": "وصول مجاني إلى DeepL (10 آلاف حرف يوميًا)",
"AI Translations (per day)": "ترجمات بالذكاء الاصطناعي (يوميًا)",
"Cloud Storage": "تخزين سحابي",
"Plus Plan": "خطة Plus",
"Includes All Free Plan Benefits": "تتضمن جميع مزايا الخطة المجانية",
"Basic AI Translations": "ترجمات أساسية بالذكاء الاصطناعي",
@@ -415,5 +412,8 @@
"Pro Plan": "خطة Pro",
"Includes All Plus Plan Benefits": "تتضمن جميع مزايا خطة Plus",
"More AI Translations": "المزيد من الترجمات بالذكاء الاصطناعي",
"DeepL Pro Access (200K characters/day)": "وصول احترافي إلى DeepL (200 ألف حرف يوميًا)"
"DeepL Pro Access (200K characters/day)": "وصول احترافي إلى DeepL (200 ألف حرف يوميًا)",
"Complete Your Subscription": "أكمل اشتراكك",
"{{percentage}}% of Cloud Sync Space Used.": "تم استخدام {{percentage}}% من مساحة المزامنة السحابية.",
"Cloud Sync Storage": "مساحة المزامنة السحابية"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Fehler beim Herunterladen des Buches: {{title}}",
"Upload Book": "Buch hochladen",
"Auto Upload Books to Cloud": "Bücher automatisch hochladen",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% des Cloud-Speichers verwendet.",
"Storage": "Speicher",
"Book deleted: {{title}}": "Buch gelöscht: {{title}}",
"Failed to delete book: {{title}}": "Fehler beim Löschen des Buches: {{title}}",
"Check Updates on Start": "Aktualisierungen beim Start prüfen",
@@ -390,7 +388,6 @@
"Unlimited Text-to-Speech Hours": "Unbegrenzte Text-to-Speech-Stunden",
"DeepL Free Access (10K characters/day)": "Kostenfreier DeepL-Zugang (10.000 Zeichen/Tag)",
"AI Translations (per day)": "KI-Übersetzungen (pro Tag)",
"Cloud Storage": "Cloud-Speicher",
"Plus Plan": "Plus-Tarif",
"Includes All Free Plan Benefits": "Enthält alle Vorteile des Gratis-Tarifs",
"Basic AI Translations": "Basis-KI-Übersetzungen",
@@ -399,5 +396,8 @@
"Pro Plan": "Pro-Tarif",
"Includes All Plus Plan Benefits": "Enthält alle Vorteile des Plus-Tarifs",
"More AI Translations": "Mehr KI-Übersetzungen",
"DeepL Pro Access (200K characters/day)": "DeepL Pro-Zugang (200.000 Zeichen/Tag)"
"DeepL Pro Access (200K characters/day)": "DeepL Pro-Zugang (200.000 Zeichen/Tag)",
"Complete Your Subscription": "Abschließen Sie Ihr Abonnement",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% des Cloud-Synchronisierungsspeichers verwendet.",
"Cloud Sync Storage": "Cloud-Speicher"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Αποτυχία λήψης βιβλίου: {{title}}",
"Upload Book": "Ανέβασμα βιβλίου",
"Auto Upload Books to Cloud": "Αυτόματη αποθήκευση στο cloud",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% της αποθήκευσης στο cloud χρησιμοποιήθηκε.",
"Storage": "Αποθήκευση",
"Book deleted: {{title}}": "Το βιβλίο με τίτλο {{title}} διαγράφηκε",
"Failed to delete book: {{title}}": "Αποτυχία διαγραφής βιβλίου: {{title}}",
"Check Updates on Start": "Αυτόματος έλεγχος ενημερώσεων",
@@ -390,7 +388,6 @@
"Unlimited Text-to-Speech Hours": "Απεριόριστες ώρες μετατροπής κειμένου σε ομιλία",
"DeepL Free Access (10K characters/day)": "Δωρεάν πρόσβαση στο DeepL (10.000 χαρακτήρες/ημέρα)",
"AI Translations (per day)": "Μεταφράσεις AI (ανά ημέρα)",
"Cloud Storage": "Αποθήκευση στο cloud",
"Plus Plan": "Πρόγραμμα Plus",
"Includes All Free Plan Benefits": "Περιλαμβάνει όλα τα οφέλη του δωρεάν προγράμματος",
"Basic AI Translations": "Βασικές μεταφράσεις AI",
@@ -399,5 +396,8 @@
"Pro Plan": "Πρόγραμμα Pro",
"Includes All Plus Plan Benefits": "Περιλαμβάνει όλα τα οφέλη του Plus προγράμματος",
"More AI Translations": "Περισσότερες μεταφράσεις AI",
"DeepL Pro Access (200K characters/day)": "Pro πρόσβαση στο DeepL (200.000 χαρακτήρες/ημέρα)"
"DeepL Pro Access (200K characters/day)": "Pro πρόσβαση στο DeepL (200.000 χαρακτήρες/ημέρα)",
"Complete Your Subscription": "Ολοκληρώστε τη συνδρομή σας",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% του χώρου συγχρονισμού στο cloud χρησιμοποιήθηκε.",
"Cloud Sync Storage": "Χώρος συγχρονισμού cloud"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Error al descargar libro: {{title}}",
"Upload Book": "Subir libro",
"Auto Upload Books to Cloud": "Subir libros automáticamente a la nube",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% del almacenamiento en la nube utilizado.",
"Storage": "Almacenamiento",
"Book deleted: {{title}}": "Libro eliminado: {{title}}",
"Failed to delete book: {{title}}": "Error al eliminar libro: {{title}}",
"Check Updates on Start": "Comprobar actualizaciones al iniciar",
@@ -393,7 +391,6 @@
"Unlimited Text-to-Speech Hours": "Horas ilimitadas de texto a voz",
"DeepL Free Access (10K characters/day)": "Acceso gratuito a DeepL (10.000 caracteres/día)",
"AI Translations (per day)": "Traducciones con IA (por día)",
"Cloud Storage": "Almacenamiento en la nube",
"Plus Plan": "Plan Plus",
"Includes All Free Plan Benefits": "Incluye todos los beneficios del plan gratuito",
"Basic AI Translations": "Traducciones básicas con IA",
@@ -403,5 +400,8 @@
"Includes All Plus Plan Benefits": "Incluye todos los beneficios del plan Plus",
"More AI Translations": "Más traducciones con IA",
"DeepL Pro Access (200K characters/day)": "Acceso Pro a DeepL (200.000 caracteres/día)",
"Your payment is being processed. This usually takes a few moments.": "Tu pago se está procesando. Normalmente tarda unos momentos."
"Your payment is being processed. This usually takes a few moments.": "Tu pago se está procesando. Normalmente tarda unos momentos.",
"Complete Your Subscription": "Completa tu suscripción",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% del espacio de sincronización en la nube utilizado.",
"Cloud Sync Storage": "Almacenamiento en la nube"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Échec du téléchargement du livre : {{title}}",
"Upload Book": "Télécharger un livre",
"Auto Upload Books to Cloud": "Télécharger automatiquement les livres dans le cloud",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% de l'espace de stockage dans le cloud utilisé.",
"Storage": "Stockage",
"Book deleted: {{title}}": "Livre supprimé : {{title}}",
"Failed to delete book: {{title}}": "Échec de la suppression du livre : {{title}}",
"Check Updates on Start": "Vérifier les mises à jour au démarrage",
@@ -394,7 +392,6 @@
"Unlimited Text-to-Speech Hours": "Heures illimitées de synthèse vocale",
"DeepL Free Access (10K characters/day)": "Accès gratuit à DeepL (10000 caractères/jour)",
"AI Translations (per day)": "Traductions IA (par jour)",
"Cloud Storage": "Stockage cloud",
"Plus Plan": "Abonnement Plus",
"Includes All Free Plan Benefits": "Inclut tous les avantages de labonnement gratuit",
"Basic AI Translations": "Traductions IA de base",
@@ -403,5 +400,8 @@
"Pro Plan": "Abonnement Pro",
"Includes All Plus Plan Benefits": "Inclut tous les avantages de labonnement Plus",
"More AI Translations": "Plus de traductions IA",
"DeepL Pro Access (200K characters/day)": "Accès Pro à DeepL (200000 caractères/jour)"
"DeepL Pro Access (200K characters/day)": "Accès Pro à DeepL (200000 caractères/jour)",
"Complete Your Subscription": "Complétez votre abonnement",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% de lespace de synchronisation cloud utilisé.",
"Cloud Sync Storage": "Stockage de synchronisation cloud"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "पुस्तक डाउनलोड करने में विफल: {{title}}",
"Upload Book": "पुस्तक अपलोड करें",
"Auto Upload Books to Cloud": "पुस्तकें बादल में स्वचालित रूप से अपलोड करें",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% क्लाउड स्टोरेज का उपयोग किया गया है।",
"Storage": "स्टोरेज",
"Book deleted: {{title}}": "पुस्तक हटाई गई: {{title}}",
"Failed to delete book: {{title}}": "पुस्तक हटाने में विफल: {{title}}",
"Check Updates on Start": "शुरू में अपडेट जांचें",
@@ -390,7 +388,6 @@
"Unlimited Text-to-Speech Hours": "अनलिमिटेड पाठ-पाठ ध्वनि घंटे",
"DeepL Free Access (10K characters/day)": "DeepL मुफ्त एक्सेस (10,000 अक्षर/दिन)",
"AI Translations (per day)": "AI अनुवाद (प्रतिदिन)",
"Cloud Storage": "क्लाउड स्टोरेज",
"Plus Plan": "Plus योजना",
"Includes All Free Plan Benefits": "सभी मुफ्त योजना के लाभ शामिल हैं",
"Basic AI Translations": "मूल AI अनुवाद",
@@ -399,5 +396,8 @@
"Pro Plan": "Pro योजना",
"Includes All Plus Plan Benefits": "सभी Plus योजना के लाभ शामिल हैं",
"More AI Translations": "और अधिक AI अनुवाद",
"DeepL Pro Access (200K characters/day)": "DeepL प्रो एक्सेस (200,000 अक्षर/दिन)"
"DeepL Pro Access (200K characters/day)": "DeepL प्रो एक्सेस (200,000 अक्षर/दिन)",
"Complete Your Subscription": "अपनी सदस्यता पूरी करें",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% क्लाउड सिंक स्पेस का उपयोग किया गया है।",
"Cloud Sync Storage": "क्लाउड सिंक स्टोरेज"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Gagal mengunduh buku: {{title}}",
"Upload Book": "Unggah Buku",
"Auto Upload Books to Cloud": "Unggah Buku Otomatis ke Cloud",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% dari Penyimpanan Cloud Digunakan.",
"Storage": "Penyimpanan",
"Book deleted: {{title}}": "Buku dihapus: {{title}}",
"Failed to delete book: {{title}}": "Gagal menghapus buku: {{title}}",
"Check Updates on Start": "Periksa Pembaruan saat Memulai",
@@ -386,7 +384,6 @@
"Unlimited Text-to-Speech Hours": "Jam Text-to-Speech Tanpa Batas",
"DeepL Free Access (10K characters/day)": "Akses DeepL Gratis (10.000 karakter/hari)",
"AI Translations (per day)": "Terjemahan AI (per hari)",
"Cloud Storage": "Penyimpanan Cloud",
"Plus Plan": "Paket Plus",
"Includes All Free Plan Benefits": "Termasuk Semua Manfaat Paket Gratis",
"Basic AI Translations": "Terjemahan AI Dasar",
@@ -395,5 +392,8 @@
"Pro Plan": "Paket Pro",
"Includes All Plus Plan Benefits": "Termasuk Semua Manfaat Paket Plus",
"More AI Translations": "Lebih Banyak Terjemahan AI",
"DeepL Pro Access (200K characters/day)": "Akses DeepL Pro (200.000 karakter/hari)"
"DeepL Pro Access (200K characters/day)": "Akses DeepL Pro (200.000 karakter/hari)",
"Complete Your Subscription": "Selesaikan Langganan Anda",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% ruang sinkronisasi cloud terpakai",
"Cloud Sync Storage": "Ruang cloud"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Download libro non riuscito: {{title}}",
"Upload Book": "Carica libro",
"Auto Upload Books to Cloud": "Carica libri automaticamente su cloud",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% dello spazio di archiviazione cloud utilizzato.",
"Storage": "Archiviazione",
"Book deleted: {{title}}": "Libro eliminato: {{title}}",
"Failed to delete book: {{title}}": "Eliminazione libro non riuscita: {{title}}",
"Check Updates on Start": "Controlla aggiornamenti all'avvio",
@@ -394,7 +392,6 @@
"Unlimited Text-to-Speech Hours": "Ore Illimitate di Sintesi Vocale",
"DeepL Free Access (10K characters/day)": "Accesso Gratuito a DeepL (10.000 caratteri/giorno)",
"AI Translations (per day)": "Traduzioni AI (al giorno)",
"Cloud Storage": "Archiviazione Cloud",
"Plus Plan": "Piano Plus",
"Includes All Free Plan Benefits": "Include Tutti i Vantaggi del Piano Gratuito",
"Basic AI Translations": "Traduzioni AI di Base",
@@ -403,5 +400,8 @@
"Pro Plan": "Piano Pro",
"Includes All Plus Plan Benefits": "Include Tutti i Vantaggi del Piano Plus",
"More AI Translations": "Più Traduzioni AI",
"DeepL Pro Access (200K characters/day)": "Accesso DeepL Pro (200.000 caratteri/giorno)"
"DeepL Pro Access (200K characters/day)": "Accesso DeepL Pro (200.000 caratteri/giorno)",
"Complete Your Subscription": "Completa l'abbonamento",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% dello spazio cloud usato",
"Cloud Sync Storage": "Spazio cloud"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "書籍のダウンロードに失敗しました:{{title}}",
"Upload Book": "書籍をアップロード",
"Auto Upload Books to Cloud": "書籍を自動的にクラウドにアップロード",
"{{percentage}}% of Cloud Storage Used.": "クラウドストレージの{{percentage}}%が使用されています。",
"Storage": "ストレージ",
"Book deleted: {{title}}": "書籍が削除されました:{{title}}",
"Failed to delete book: {{title}}": "書籍の削除に失敗しました:{{title}}",
"Check Updates on Start": "開始時に更新を確認",
@@ -386,7 +384,6 @@
"Unlimited Text-to-Speech Hours": "無制限の音声読み上げ時間",
"DeepL Free Access (10K characters/day)": "DeepL無料アクセス(1日1万文字)",
"AI Translations (per day)": "AI翻訳(1日あたり)",
"Cloud Storage": "クラウドストレージ",
"Plus Plan": "Plusプラン",
"Includes All Free Plan Benefits": "無料プランの全特典を含む",
"Basic AI Translations": "基本的なAI翻訳",
@@ -395,5 +392,8 @@
"Pro Plan": "Proプラン",
"Includes All Plus Plan Benefits": "Plusプランの全特典を含む",
"More AI Translations": "さらに多くのAI翻訳",
"DeepL Pro Access (200K characters/day)": "DeepL Proアクセス(1日20万文字)"
"DeepL Pro Access (200K characters/day)": "DeepL Proアクセス(1日20万文字)",
"Complete Your Subscription": "サブスクリプションを完了する",
"{{percentage}}% of Cloud Sync Space Used.": "クラウド同期スペースの使用量は{{percentage}}%です。",
"Cloud Sync Storage": "クラウド同期容量"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "책 다운로드 실패: {{title}}",
"Upload Book": "책 업로드",
"Auto Upload Books to Cloud": "책 자동으로 클라우드에 업로드",
"{{percentage}}% of Cloud Storage Used.": "클라우드 저장소 사용량 {{percentage}}%.",
"Storage": "저장소",
"Book deleted: {{title}}": "책 삭제됨: {{title}}",
"Failed to delete book: {{title}}": "책 삭제 실패: {{title}}",
"Check Updates on Start": "시작 시 업데이트 확인",
@@ -386,7 +384,6 @@
"Unlimited Text-to-Speech Hours": "무제한 음성 변환 시간",
"DeepL Free Access (10K characters/day)": "DeepL 무료 액세스 (일 1만자)",
"AI Translations (per day)": "AI 번역 (일 기준)",
"Cloud Storage": "클라우드 저장소",
"Plus Plan": "Plus 요금제",
"Includes All Free Plan Benefits": "무료 요금제의 모든 혜택 포함",
"Basic AI Translations": "기본 AI 번역",
@@ -395,5 +392,8 @@
"Pro Plan": "Pro 요금제",
"Includes All Plus Plan Benefits": "Plus 요금제의 모든 혜택 포함",
"More AI Translations": "더 많은 AI 번역",
"DeepL Pro Access (200K characters/day)": "DeepL Pro 액세스 (일 20만자)"
"DeepL Pro Access (200K characters/day)": "DeepL Pro 액세스 (일 20만자)",
"Complete Your Subscription": "구독 완료",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}%의 클라우드 동기화 공간 사용됨.",
"Cloud Sync Storage": "클라우드 저장 공간"
}
@@ -57,8 +57,6 @@
"Import Books": "Boeken importeren",
"Select Multiple Books": "Meerdere boeken selecteren",
"Select Books": "Boeken selecteren",
"Storage": "Opslag",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% van cloudopslag gebruikt.",
"Logged in as {{userDisplayName}}": "Ingelogd als {{userDisplayName}}",
"Logged in": "Ingelogd",
"Account": "Account",
@@ -388,7 +386,6 @@
"Unlimited Text-to-Speech Hours": "Onbeperkte tekst-naar-spraak-uren",
"DeepL Free Access (10K characters/day)": "DeepL gratis toegang (10.000 tekens/dag)",
"AI Translations (per day)": "AI-vertalingen (per dag)",
"Cloud Storage": "Cloudopslag",
"Plus Plan": "Plus-abonnement",
"Includes All Free Plan Benefits": "Bevat alle voordelen van het gratis abonnement",
"Basic AI Translations": "Basis AI-vertalingen",
@@ -399,5 +396,8 @@
"More AI Translations": "Meer AI-vertalingen",
"DeepL Pro Access (200K characters/day)": "DeepL Pro toegang (200.000 tekens/dag)",
"We couldn't process your subscription. Please try again or contact support if the issue persists.": "We konden je abonnement niet verwerken. Probeer het opnieuw of neem contact op met support als het probleem blijft bestaan.",
"Thank you for your subscription! Your payment has been processed successfully.": "Bedankt voor je abonnement! Je betaling is succesvol verwerkt."
"Thank you for your subscription! Your payment has been processed successfully.": "Bedankt voor je abonnement! Je betaling is succesvol verwerkt.",
"Complete Your Subscription": "Voltooi uw abonnement",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% van de cloud-synchronisatie ruimte gebruikt.",
"Cloud Sync Storage": "Cloud Sync Opslag"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Nie udało się pobrać książki: {{title}}",
"Upload Book": "Prześlij książkę",
"Auto Upload Books to Cloud": "Automatycznie przesyłaj książki do chmury",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% przestrzeni dyskowej w chmurze użytej.",
"Storage": "Przechowywanie",
"Book deleted: {{title}}": "Książka usunięta: {{title}}",
"Failed to delete book: {{title}}": "Nie udało się usunąć książki: {{title}}",
"Check Updates on Start": "Sprawdź aktualizacje przy starcie",
@@ -398,7 +396,6 @@
"Unlimited Text-to-Speech Hours": "Nieograniczone godziny zamiany tekstu na mowę",
"DeepL Free Access (10K characters/day)": "Darmowy dostęp do DeepL (10 tys. znaków/dzień)",
"AI Translations (per day)": "Tłumaczenia AI (na dzień)",
"Cloud Storage": "Przechowywanie w chmurze",
"Plus Plan": "Plan Plus",
"Includes All Free Plan Benefits": "Zawiera wszystkie korzyści planu darmowego",
"Basic AI Translations": "Podstawowe tłumaczenia AI",
@@ -407,5 +404,8 @@
"Pro Plan": "Plan Pro",
"Includes All Plus Plan Benefits": "Zawiera wszystkie korzyści planu Plus",
"More AI Translations": "Więcej tłumaczeń AI",
"DeepL Pro Access (200K characters/day)": "DeepL Pro (200 tys. znaków/dzień)"
"DeepL Pro Access (200K characters/day)": "DeepL Pro (200 tys. znaków/dzień)",
"Complete Your Subscription": "Ukończ subskrypcję",
"{{percentage}}% of Cloud Sync Space Used.": "Użyto {{percentage}}% przestrzeni synchronizacji w chmurze.",
"Cloud Sync Storage": "Miejsce w chmurze"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Falha ao baixar livro: {{title}}",
"Upload Book": "Enviar Livro",
"Auto Upload Books to Cloud": "Enviar Livros para a Nuvem Automaticamente",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% do Armazenamento na Nuvem Usado.",
"Storage": "Armazenamento",
"Book deleted: {{title}}": "Livro excluído: {{title}}",
"Failed to delete book: {{title}}": "Falha ao excluir livro: {{title}}",
"Check Updates on Start": "Verificar Atualizações ao Iniciar",
@@ -394,7 +392,6 @@
"Unlimited Text-to-Speech Hours": "Horas ilimitadas de texto para fala",
"DeepL Free Access (10K characters/day)": "Acesso gratuito ao DeepL (10 mil caracteres/dia)",
"AI Translations (per day)": "Traduções com IA (por dia)",
"Cloud Storage": "Armazenamento na nuvem",
"Plus Plan": "Plano Plus",
"Includes All Free Plan Benefits": "Inclui todos os benefícios do plano gratuito",
"Basic AI Translations": "Traduções básicas com IA",
@@ -403,5 +400,8 @@
"Pro Plan": "Plano Pro",
"Includes All Plus Plan Benefits": "Inclui todos os benefícios do plano Plus",
"More AI Translations": "Mais traduções com IA",
"DeepL Pro Access (200K characters/day)": "Acesso ao DeepL Pro (200 mil caracteres/dia)"
"DeepL Pro Access (200K characters/day)": "Acesso ao DeepL Pro (200 mil caracteres/dia)",
"Complete Your Subscription": "Complete sua assinatura",
"{{percentage}}% of Cloud Sync Space Used.": "{{percentage}}% do espaço de sincronização na nuvem utilizado.",
"Cloud Sync Storage": "Armazenamento na Nuvem"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Не удалось скачать книгу: {{title}}",
"Upload Book": "Загрузить книгу",
"Auto Upload Books to Cloud": "Автоматическая загрузка книг в облако",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% использовано облачного хранилища.",
"Storage": "Хранилище",
"Book deleted: {{title}}": "Книга удалена: {{title}}",
"Failed to delete book: {{title}}": "Не удалось удалить книгу: {{title}}",
"Check Updates on Start": "Проверять обновления при запуске",
@@ -398,7 +396,6 @@
"Unlimited Text-to-Speech Hours": "Неограниченные часы озвучивания текста",
"DeepL Free Access (10K characters/day)": "Бесплатный доступ к DeepL (10 тыс. символов/день)",
"AI Translations (per day)": "Переводы ИИ (в день)",
"Cloud Storage": "Облачное хранилище",
"Plus Plan": "План Plus",
"Includes All Free Plan Benefits": "Включает все преимущества бесплатного плана",
"Basic AI Translations": "Базовые переводы ИИ",
@@ -407,5 +404,8 @@
"Pro Plan": "План Pro",
"Includes All Plus Plan Benefits": "Включает все преимущества плана Plus",
"More AI Translations": "Больше переводов ИИ",
"DeepL Pro Access (200K characters/day)": "Доступ к DeepL Pro (200 тыс. символов/день)"
"DeepL Pro Access (200K characters/day)": "Доступ к DeepL Pro (200 тыс. символов/день)",
"Complete Your Subscription": "Завершите подписку",
"{{percentage}}% of Cloud Sync Space Used.": "Использовано {{percentage}}% облачного пространства для синхронизации.",
"Cloud Sync Storage": "Облачное хранилище"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Kitap indirilemedi: {{title}}",
"Upload Book": "Kitap Yükle",
"Auto Upload Books to Cloud": "Kitapları Otomatik Olarak Buluta Yükle",
"{{percentage}}% of Cloud Storage Used.": "Bulut Depolamanın %{{percentage}}'si kullanıldı.",
"Storage": "Depolama",
"Book deleted: {{title}}": "Kitap silindi: {{title}}",
"Failed to delete book: {{title}}": "Kitap silinemedi: {{title}}",
"Check Updates on Start": "Başlangıçta Güncellemeleri Kontrol Et",
@@ -390,7 +388,6 @@
"Unlimited Text-to-Speech Hours": "Sınırsız Sesli Okuma Süresi",
"DeepL Free Access (10K characters/day)": "DeepL Ücretsiz Erişim (Günde 10K karakter)",
"AI Translations (per day)": "AI Çevirileri (günlük)",
"Cloud Storage": "Bulut Depolama",
"Plus Plan": "Plus Plan",
"Includes All Free Plan Benefits": "Tüm Ücretsiz Plan Özelliklerini İçerir",
"Basic AI Translations": "Temel AI Çevirileri",
@@ -399,5 +396,8 @@
"Pro Plan": "Pro Plan",
"Includes All Plus Plan Benefits": "Tüm Plus Plan Özelliklerini İçerir",
"More AI Translations": "Daha Fazla AI Çevirisi",
"DeepL Pro Access (200K characters/day)": "DeepL Pro Erişim (Günde 200K karakter)"
"DeepL Pro Access (200K characters/day)": "DeepL Pro Erişim (Günde 200K karakter)",
"Complete Your Subscription": "Aboneliğinizi Tamamlayın",
"{{percentage}}% of Cloud Sync Space Used.": "Bulut Senkronizasyon Alanının %{{percentage}}'si Kullanıldı.",
"Cloud Sync Storage": "Bulut Senkronizasyon Depolama"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Не вдалося завантажити книгу: {{title}}",
"Upload Book": "Завантажити книгу",
"Auto Upload Books to Cloud": "Автоматично завантажувати книги в хмару",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% використано хмарного сховища.",
"Storage": "Сховище",
"Book deleted: {{title}}": "Книгу видалено: {{title}}",
"Failed to delete book: {{title}}": "Не вдалося видалити книгу: {{title}}",
"Check Updates on Start": "Перевіряти оновлення при запуску",
@@ -398,7 +396,6 @@
"Unlimited Text-to-Speech Hours": "Необмежені години озвучення тексту",
"DeepL Free Access (10K characters/day)": "Безкоштовний доступ до DeepL (10 тис. символів/день)",
"AI Translations (per day)": "AI-переклади (на день)",
"Cloud Storage": "Хмарне сховище",
"Plus Plan": "План Plus",
"Includes All Free Plan Benefits": "Включає всі переваги Безкоштовного плану",
"Basic AI Translations": "Базові AI-переклади",
@@ -407,5 +404,8 @@
"Pro Plan": "План Pro",
"Includes All Plus Plan Benefits": "Включає всі переваги плану Plus",
"More AI Translations": "Більше AI-перекладів",
"DeepL Pro Access (200K characters/day)": "DeepL Pro доступ (200 тис. символів/день)"
"DeepL Pro Access (200K characters/day)": "DeepL Pro доступ (200 тис. символів/день)",
"Complete Your Subscription": "Завершіть підписку",
"{{percentage}}% of Cloud Sync Space Used.": "Використано {{percentage}}% хмарного сховища.",
"Cloud Sync Storage": "Облачне хранилище"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "Không thể tải về sách: {{title}}",
"Upload Book": "Tải lên sách",
"Auto Upload Books to Cloud": "Tự động tải sách lên đám mây",
"{{percentage}}% of Cloud Storage Used.": "{{percentage}}% dung lượng lưu trữ đám mây đã sử dụng.",
"Storage": "Lưu trữ",
"Book deleted: {{title}}": "Sách đã xóa: {{title}}",
"Failed to delete book: {{title}}": "Không thể xóa sách: {{title}}",
"Check Updates on Start": "Kiểm tra cập nhật khi khởi động",
@@ -386,7 +384,6 @@
"Unlimited Text-to-Speech Hours": "Không giới hạn giờ chuyển văn bản thành giọng nói",
"DeepL Free Access (10K characters/day)": "Truy cập DeepL miễn phí (10.000 ký tự/ngày)",
"AI Translations (per day)": "Dịch AI (mỗi ngày)",
"Cloud Storage": "Lưu trữ đám mây",
"Plus Plan": "Gói Plus",
"Includes All Free Plan Benefits": "Bao gồm tất cả lợi ích của gói miễn phí",
"Basic AI Translations": "Dịch AI cơ bản",
@@ -395,5 +392,8 @@
"Pro Plan": "Gói Pro",
"Includes All Plus Plan Benefits": "Bao gồm tất cả lợi ích của gói Plus",
"More AI Translations": "Thêm dịch AI",
"DeepL Pro Access (200K characters/day)": "Truy cập DeepL Pro (200.000 ký tự/ngày)"
"DeepL Pro Access (200K characters/day)": "Truy cập DeepL Pro (200.000 ký tự/ngày)",
"Complete Your Subscription": "Hoàn tất đăng ký của bạn",
"{{percentage}}% of Cloud Sync Space Used.": "Đã sử dụng {{percentage}}% không gian đồng bộ đám mây.",
"Cloud Sync Storage": "Lưu trữ đồng bộ đám mây"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "下载书籍失败:{{title}}",
"Upload Book": "上传书籍",
"Auto Upload Books to Cloud": "自动上传书籍到云端",
"{{percentage}}% of Cloud Storage Used.": "已使用 {{percentage}}% 云存储空间",
"Storage": "存储空间",
"Book deleted: {{title}}": "书籍已删除:{{title}}",
"Failed to delete book: {{title}}": "删除书籍失败:{{title}}",
"Check Updates on Start": "启动时检查更新",
@@ -386,7 +384,6 @@
"Unlimited Text-to-Speech Hours": "无限文本转语音时长",
"DeepL Free Access (10K characters/day)": "DeepL 免费额度(每日1万字符)",
"AI Translations (per day)": "AI 翻译(每日)",
"Cloud Storage": "云存储",
"Plus Plan": "Plus 套餐",
"Includes All Free Plan Benefits": "包含所有免费套餐功能",
"Basic AI Translations": "基础 AI 翻译",
@@ -395,5 +392,8 @@
"Pro Plan": "Pro 套餐",
"Includes All Plus Plan Benefits": "包含所有 Plus 套餐功能",
"More AI Translations": "更多 AI 翻译",
"DeepL Pro Access (200K characters/day)": "DeepL Pro(每日20万字符)"
"DeepL Pro Access (200K characters/day)": "DeepL Pro(每日20万字符)",
"Complete Your Subscription": "完成您的订阅",
"{{percentage}}% of Cloud Sync Space Used.": "已使用 {{percentage}}% 的云同步空间。",
"Cloud Sync Storage": "云同步空间"
}
@@ -126,8 +126,6 @@
"Failed to download book: {{title}}": "書籍下載失敗:{{title}}",
"Upload Book": "上傳書籍",
"Auto Upload Books to Cloud": "自動上傳書籍到雲端",
"{{percentage}}% of Cloud Storage Used.": "已使用 {{percentage}}% 雲存儲空間",
"Storage": "存儲空間",
"Book deleted: {{title}}": "書籍已刪除:{{title}}",
"Failed to delete book: {{title}}": "書籍刪除失敗:{{title}}",
"Check Updates on Start": "啟動時檢查更新",
@@ -386,7 +384,6 @@
"Unlimited Text-to-Speech Hours": "無限文字轉語音時數",
"DeepL Free Access (10K characters/day)": "DeepL 免費使用(每日1萬字)",
"AI Translations (per day)": "AI 翻譯(每日)",
"Cloud Storage": "雲端儲存",
"Plus Plan": "Plus 方案",
"Includes All Free Plan Benefits": "包含所有免費方案功能",
"Basic AI Translations": "基本 AI 翻譯",
@@ -395,5 +392,8 @@
"Pro Plan": "Pro 方案",
"Includes All Plus Plan Benefits": "包含所有 Plus 方案功能",
"More AI Translations": "更多 AI 翻譯",
"DeepL Pro Access (200K characters/day)": "DeepL Pro(每日20萬字)"
"DeepL Pro Access (200K characters/day)": "DeepL Pro(每日20萬字)",
"Complete Your Subscription": "完成您的訂閱",
"{{percentage}}% of Cloud Sync Space Used.": "已使用 {{percentage}}% 的雲同步空間。",
"Cloud Sync Storage": "雲同步空間"
}
@@ -4,7 +4,7 @@ import { validateUserAndToken } from '@/utils/access';
import { supabase } from '@/utils/supabase';
export async function POST(request: NextRequest) {
const { priceId, metadata = {} } = await request.json();
const { priceId, embedded = true, metadata = {} } = await request.json();
const { user, token } = await validateUserAndToken(request.headers.get('authorization'));
if (!user || !token) {
@@ -42,7 +42,10 @@ export async function POST(request: NextRequest) {
}
const stripe = getStripe();
const successUrl = `${request.headers.get('origin')}/user/subscription/success?session_id={CHECKOUT_SESSION_ID}`;
const returnUrl = `${request.headers.get('origin')}/user`;
const session = await stripe.checkout.sessions.create({
ui_mode: embedded ? 'embedded' : 'hosted',
customer: customerId,
mode: 'subscription',
payment_method_types: ['card'],
@@ -53,11 +56,16 @@ export async function POST(request: NextRequest) {
},
],
metadata: enhancedMetadata,
success_url: `${request.headers.get('origin')}/user/subscription/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${request.headers.get('origin')}/user`,
success_url: embedded ? undefined : successUrl,
cancel_url: embedded ? undefined : returnUrl,
redirect_on_completion: embedded ? 'never' : undefined,
});
return NextResponse.json({ sessionId: session.id, url: session.url });
return NextResponse.json({
url: session.url,
sessionId: session.id,
clientSecret: session.client_secret,
});
} catch (error) {
console.error(error);
return NextResponse.json({ error: 'Error creating checkout session' }, { status: 500 });
@@ -0,0 +1,53 @@
'use client';
import clsx from 'clsx';
import React, { useCallback } from 'react';
import { EmbeddedCheckoutProvider, EmbeddedCheckout } from '@stripe/react-stripe-js';
import { getStripe } from '@/libs/stripe/client';
import { useTranslation } from '@/hooks/useTranslation';
interface CheckoutProps {
clientSecret: string;
sessionId: string;
planName?: string;
className?: string;
onSuccess?: (sessionId: string) => void;
}
const Checkout: React.FC<CheckoutProps> = ({
clientSecret,
sessionId,
planName,
onSuccess,
className = '',
}) => {
const _ = useTranslation();
const stripe = getStripe();
const options = {
clientSecret,
onComplete: useCallback(() => {
onSuccess?.(sessionId);
}, [onSuccess, sessionId]),
};
return (
<div className={clsx('w-full', className)}>
<div className='mb-4 flex items-center justify-center'>
<h3 className='text-center text-lg font-semibold'>
{planName
? _('Upgrade to {{plan}}', { plan: _(planName) })
: _('Complete Your Subscription')}
</h3>
</div>
<div className='border-base-300 overflow-hidden rounded-lg border'>
<EmbeddedCheckoutProvider stripe={stripe} options={options}>
<EmbeddedCheckout />
</EmbeddedCheckoutProvider>
</div>
</div>
);
};
export default Checkout;
@@ -1,9 +1,9 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useEnv } from '@/context/EnvContext';
import { debounce } from '@/utils/debounce';
import { isWebAppPlatform } from '@/services/environment';
import { getPlanDetails } from '../utils/plan';
import { AvailablePlan } from '../page';
import { UserPlan } from '@/types/user';
import { AvailablePlan } from '../page';
import PlanNavigation from './PlanNavigation';
import PlanCard from './PlanCard';
import PlanIndicators from './PlanIndicators';
@@ -19,6 +19,7 @@ const PlansComparison: React.FC<PlansComparisonProps> = ({
userPlan,
onSubscribe,
}) => {
const { appService } = useEnv();
const [currentPlanIndex, setCurrentPlanIndex] = useState(0);
const [userPlanIndex, setUserPlanIndex] = useState(0);
const plansScrollRef = useRef<HTMLDivElement>(null);
@@ -137,7 +138,7 @@ const PlansComparison: React.FC<PlansComparisonProps> = ({
<PlanCard
key={plan.plan}
plan={plan}
comingSoon={!isWebAppPlatform()}
comingSoon={appService?.isIOSApp}
isUserPlan={plan.plan === userPlan}
upgradable={index > userPlanIndex}
index={index}
+102 -33
View File
@@ -2,7 +2,8 @@
import clsx from 'clsx';
import Stripe from 'stripe';
import React, { useState, useEffect } from 'react';
import posthog from 'posthog-js';
import React, { useState, useEffect, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { useEnv } from '@/context/EnvContext';
import { useAuth } from '@/context/AuthContext';
@@ -15,15 +16,18 @@ import { navigateToLibrary } from '@/utils/nav';
import { deleteUser } from '@/libs/user';
import { eventDispatcher } from '@/utils/event';
import { getStripe } from '@/libs/stripe/client';
import { getAPIBaseUrl } from '@/services/environment';
import { getAPIBaseUrl, isTauriAppPlatform, isWebAppPlatform } from '@/services/environment';
import { openUrl } from '@tauri-apps/plugin-opener';
import { getAccessToken } from '@/utils/access';
import { getPlanDetails } from './utils/plan';
import { Toast } from '@/components/Toast';
import Spinner from '@/components/Spinner';
import ProfileHeader from './components/Header';
import UserInfo from './components/UserInfo';
import UsageStats from './components/UsageStats';
import PlansComparison from './components/PlansComparison';
import AccountActions from './components/AccountActions';
import Checkout from './components/Checkout';
const WEB_STRIPE_PLANS_URL = `${getAPIBaseUrl()}/stripe/plans`;
const WEB_STRIPE_CHECKOUT_URL = `${getAPIBaseUrl()}/stripe/checkout`;
@@ -38,6 +42,12 @@ export type AvailablePlan = {
product: Stripe.Product;
};
type CheckoutState = {
clientSecret: string;
sessionId: string;
planName: string;
};
const ProfilePage = () => {
const _ = useTranslation();
const router = useRouter();
@@ -45,7 +55,14 @@ const ProfilePage = () => {
const { token, user, logout } = useAuth();
const { settings, setSettings, saveSettings } = useSettingsStore();
const [loading, setLoading] = useState(false);
const [availablePlans, setAvailablePlans] = useState<AvailablePlan[]>([]);
const [showEmbeddedCheckout, setShowEmbeddedCheckout] = useState(false);
const [checkoutState, setCheckoutState] = useState<CheckoutState>({
clientSecret: '',
sessionId: '',
planName: '',
});
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
@@ -55,7 +72,11 @@ const ProfilePage = () => {
const { quotas, userPlan } = useQuotaStats();
const handleGoBack = () => {
navigateToLibrary(router);
if (showEmbeddedCheckout) {
setShowEmbeddedCheckout(false);
} else {
navigateToLibrary(router);
}
};
const handleLogout = () => {
@@ -86,26 +107,54 @@ const ProfilePage = () => {
console.error('Stripe not loaded');
return;
}
const { sessionId, url } = await fetch(WEB_STRIPE_CHECKOUT_URL, {
setLoading(true);
const isEmbeddedCheckout = isTauriAppPlatform();
const { sessionId, clientSecret, url } = await fetch(WEB_STRIPE_CHECKOUT_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ priceId }),
body: JSON.stringify({ priceId, embedded: isEmbeddedCheckout }),
}).then((res) => res.json());
setLoading(false);
if (url) {
window.location.href = url;
} else {
const selectedPlan = availablePlans.find((plan) => plan.price_id === priceId)!;
const planName = selectedPlan.product.name;
if (isEmbeddedCheckout && sessionId && clientSecret) {
setShowEmbeddedCheckout(true);
setCheckoutState({
planName,
clientSecret,
sessionId,
});
} else if (url) {
if (isWebAppPlatform()) {
window.location.href = url;
} else if (isTauriAppPlatform()) {
await openUrl(url);
}
} else if (sessionId) {
const result = await stripe.redirectToCheckout({ sessionId });
if (result.error) {
console.error(result.error);
posthog.capture('checkout_error', {
error: 'Failed to redirect to checkout',
});
}
} else {
console.error('No sessionId or url returned from checkout API');
posthog.capture('checkout_error', {
error: 'No sessionId or url returned from checkout API',
});
}
};
const handleCheckoutSuccess = useCallback((sessionId: string) => {
setShowEmbeddedCheckout(false);
router.push(`/user/subscription/success?session_id=${sessionId}`);
}, []);
const handleManageSubscription = async () => {
const token = await getAccessToken();
const response = await fetch(WEB_STRIPE_PORTAL_URL, {
@@ -127,7 +176,11 @@ const ProfilePage = () => {
return;
}
window.location.href = url;
if (isWebAppPlatform()) {
window.location.href = url;
} else if (isTauriAppPlatform()) {
await openUrl(url);
}
};
useEffect(() => {
@@ -168,31 +221,47 @@ const ProfilePage = () => {
>
<ProfileHeader onGoBack={handleGoBack} />
<div className='w-full min-w-60 max-w-4xl px-4 py-10'>
<div className='bg-base-200 overflow-hidden rounded-lg p-2 shadow-md sm:p-6'>
<div className='flex flex-col gap-y-8 p-2 sm:p-6'>
<UserInfo
avatarUrl={avatarUrl}
userFullName={userFullName}
userEmail={userEmail}
planDetails={planDetails}
/>
<UsageStats quotas={quotas} />
<PlansComparison
availablePlans={availablePlans}
userPlan={userPlan}
onSubscribe={handleSubscribe}
/>
<AccountActions
userPlan={userPlan}
onLogout={handleLogout}
onConfirmDelete={handleConfirmDelete}
onManageSubscription={handleManageSubscription}
{loading && (
<div className='fixed inset-0 z-50 flex items-center justify-center'>
<Spinner loading />
</div>
)}
{showEmbeddedCheckout ? (
<div className='bg-base-100 rounded-lg p-4'>
<Checkout
clientSecret={checkoutState.clientSecret}
sessionId={checkoutState.sessionId}
planName={checkoutState.planName}
onSuccess={handleCheckoutSuccess}
/>
</div>
</div>
) : (
<div className='bg-base-200 overflow-hidden rounded-lg p-2 shadow-md sm:p-6'>
<div className='flex flex-col gap-y-8 p-2 sm:p-6'>
<UserInfo
avatarUrl={avatarUrl}
userFullName={userFullName}
userEmail={userEmail}
planDetails={planDetails}
/>
<UsageStats quotas={quotas} />
<PlansComparison
availablePlans={availablePlans}
userPlan={userPlan}
onSubscribe={handleSubscribe}
/>
<AccountActions
userPlan={userPlan}
onLogout={handleLogout}
onConfirmDelete={handleConfirmDelete}
onManageSubscription={handleManageSubscription}
/>
</div>
</div>
)}
</div>
<Toast />
</div>
+3 -3
View File
@@ -35,8 +35,8 @@ export const getPlanDetails = (
_('500 MB Cloud Sync Space'),
],
limits: {
[_('Cloud Sync Storage')]: '500 MB',
[_('AI Translations (per day)')]: '10K',
[_('Cloud Storage')]: '500 MB',
},
};
case 'plus':
@@ -55,8 +55,8 @@ export const getPlanDetails = (
_('2 GB Cloud Sync Space'),
],
limits: {
[_('Cloud Sync Storage')]: '2 GB',
[_('AI Translations (per day)')]: '50K',
[_('Cloud Storage')]: '2 GB',
},
};
case 'pro':
@@ -76,8 +76,8 @@ export const getPlanDetails = (
_('10 GB Cloud Sync Space'),
],
limits: {
[_('Cloud Sync Storage')]: '10 GB',
[_('AI Translations (per day)')]: '200K',
[_('Cloud Storage')]: '10 GB',
},
};
default:
@@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import Image from 'next/image';
import packageJson from '../../package.json';
import { useEnv } from '@/context/EnvContext';
import { useTranslation } from '@/hooks/useTranslation';
import { checkForAppUpdates } from '@/helpers/updater';
import { parseWebViewVersion } from '@/utils/ua';
import { getAppVersion } from '@/utils/version';
import Dialog from './Dialog';
import Link from './Link';
@@ -83,7 +83,7 @@ export const AboutWindow = () => {
<div className='flex select-text flex-col items-center'>
<h2 className='mb-2 text-2xl font-bold'>Readest</h2>
<p className='text-neutral-content text-center text-sm'>
{_('Version {{version}}', { version: packageJson.version })} {`(${browserInfo})`}
{_('Version {{version}}', { version: getAppVersion() })} {`(${browserInfo})`}
</p>
</div>
<div className='h-5'>
@@ -1,19 +1,15 @@
'use client';
import { useEffect, useState } from 'react';
import { IconContext } from 'react-icons';
import { AuthProvider } from '@/context/AuthContext';
import { EnvProvider } from '@/context/EnvContext';
import { CSPostHogProvider } from '@/context/PHContext';
import { SyncProvider } from '@/context/SyncContext';
import { useDefaultIconSize } from '@/hooks/useResponsiveSize';
import { isWebAppPlatform } from '@/services/environment';
const Providers = ({ children }: { children: React.ReactNode }) => {
const iconSize = useDefaultIconSize();
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
return mounted || isWebAppPlatform() ? (
return (
<CSPostHogProvider>
<EnvProvider>
<AuthProvider>
@@ -23,7 +19,7 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
</AuthProvider>
</EnvProvider>
</CSPostHogProvider>
) : null;
);
};
export default Providers;
@@ -10,11 +10,11 @@ import { fetch } from '@tauri-apps/plugin-http';
import { useTranslator } from '@/hooks/useTranslator';
import { useTranslation } from '@/hooks/useTranslation';
import { useSearchParams } from 'next/navigation';
import { getAppVersion } from '@/utils/version';
import { tauriDownload } from '@/utils/transfer';
import { installPackage } from '@/utils/bridge';
import { getLocale } from '@/utils/misc';
import { READEST_UPDATER_FILE, READEST_CHANGELOG_FILE } from '@/services/constants';
import packageJson from '../../package.json';
import Dialog from '@/components/Dialog';
interface ReleaseNotes {
@@ -68,7 +68,7 @@ export const UpdaterContent = ({ version }: { version?: string }) => {
});
const { appService } = useEnv();
const searchParams = useSearchParams();
const currentVersion = packageJson.version;
const currentVersion = getAppVersion();
const resolvedVersion = version ?? searchParams?.get('version') ?? '';
const [update, setUpdate] = useState<GenericUpdate | Update | null>(null);
const [isMounted, setIsMounted] = useState(false);
@@ -93,7 +93,7 @@ export const UpdaterContent = ({ version }: { version?: string }) => {
const checkAndroidUpdate = async () => {
const response = await fetch(READEST_UPDATER_FILE);
const data = await response.json();
if (semver.gt(data.version, packageJson.version)) {
if (semver.gt(data.version, currentVersion)) {
const OS_ARCH = osArch();
const platformKey = OS_ARCH === 'aarch64' ? 'android-arm64' : 'android-universal';
const arch = OS_ARCH === 'aarch64' ? 'arm64' : 'universal';
@@ -101,7 +101,7 @@ export const UpdaterContent = ({ version }: { version?: string }) => {
const cachePrefix = appService?.fs.getPrefix('Cache');
const apkFilePath = `${cachePrefix}/Readest_${data.version}_${arch}.apk`;
setUpdate({
currentVersion: packageJson.version,
currentVersion,
version: data.version,
date: data.date,
body: data.notes,
+7 -1
View File
@@ -1,9 +1,10 @@
'use client';
import posthog from 'posthog-js';
import { ReactNode } from 'react';
import { ReactNode, useEffect } from 'react';
import { PostHogProvider } from 'posthog-js/react';
import { TELEMETRY_OPT_OUT_KEY } from '@/utils/telemetry';
import { getAppVersion } from '@/utils/version';
const shouldDisablePostHog = () => {
if (typeof window === 'undefined') return false;
@@ -27,5 +28,10 @@ if (typeof window !== 'undefined' && process.env['NODE_ENV'] === 'production' &&
}
}
export const CSPostHogProvider = ({ children }: { children: ReactNode }) => {
useEffect(() => {
posthog.register_for_session({
$app_version: getAppVersion(),
});
}, []);
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
};
+2 -2
View File
@@ -5,8 +5,8 @@ import { fetch } from '@tauri-apps/plugin-http';
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { TranslationFunc } from '@/hooks/useTranslation';
import { setUpdaterWindowVisible } from '@/components/UpdaterWindow';
import { getAppVersion } from '@/utils/version';
import { CHECK_UPDATE_INTERVAL_SEC, READEST_UPDATER_FILE } from '@/services/constants';
import packageJson from '../../package.json';
const LAST_CHECK_KEY = 'lastAppUpdateCheck';
@@ -49,7 +49,7 @@ export const checkForAppUpdates = async (
try {
const response = await fetch(READEST_UPDATER_FILE, { connectTimeout: 5000 });
const data = await response.json();
const isNewer = semver.gt(data.version, packageJson.version);
const isNewer = semver.gt(data.version, getAppVersion());
if (isNewer && ('android-arm64' in data.platforms || 'android-universal' in data.platforms)) {
setUpdaterWindowVisible(true, data.version);
}
+2 -2
View File
@@ -16,8 +16,8 @@ export const useQuotaStats = () => {
const userPlan = getUserPlan(token);
const storagPlan = getStoragePlanData(token);
const storageQuota: QuotaType = {
name: _('Storage'),
tooltip: _('{{percentage}}% of Cloud Storage Used.', {
name: _('Cloud Sync Storage'),
tooltip: _('{{percentage}}% of Cloud Sync Space Used.', {
percentage: Math.round((storagPlan.usage / storagPlan.quota) * 100),
}),
used: Math.round(storagPlan.usage / 1024 / 1024),
+2 -2
View File
@@ -41,7 +41,7 @@ export class SyncClient {
Authorization: `Bearer ${token}`,
},
},
5000,
8000,
);
if (!res.ok) {
@@ -70,7 +70,7 @@ export class SyncClient {
},
body: JSON.stringify(payload),
},
5000,
8000,
);
if (!res.ok) {
+1 -1
View File
@@ -2,7 +2,7 @@ import { getAccessToken } from './access';
export const fetchWithTimeout = (url: string, options: RequestInit, timeout = 8000) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const id = setTimeout(() => controller.abort('Request timed out'), timeout);
return fetch(url, {
...options,
+5
View File
@@ -0,0 +1,5 @@
import packageJson from '../../package.json';
export const getAppVersion = () => {
return packageJson.version;
};
+47 -20
View File
@@ -53,18 +53,21 @@ importers:
'@opennextjs/cloudflare':
specifier: ^1.3.1
version: 1.3.1(wrangler@4.21.0)
'@stripe/react-stripe-js':
specifier: ^3.7.0
version: 3.7.0(@stripe/stripe-js@7.4.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@stripe/stripe-js':
specifier: ^7.4.0
version: 7.4.0
'@supabase/auth-ui-react':
specifier: ^0.4.7
version: 0.4.7(@supabase/supabase-js@2.49.10)
version: 0.4.7(@supabase/supabase-js@2.50.2)
'@supabase/auth-ui-shared':
specifier: ^0.1.8
version: 0.1.8(@supabase/supabase-js@2.49.10)
version: 0.1.8(@supabase/supabase-js@2.50.2)
'@supabase/supabase-js':
specifier: ^2.49.10
version: 2.49.10
specifier: ^2.50.2
version: 2.50.2
'@tauri-apps/api':
specifier: 2.6.0
version: 2.6.0
@@ -2545,12 +2548,19 @@ packages:
'@stitches/core@1.2.8':
resolution: {integrity: sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==}
'@stripe/react-stripe-js@3.7.0':
resolution: {integrity: sha512-PYls/2S9l0FF+2n0wHaEJsEU8x7CmBagiH7zYOsxbBlLIHEsqUIQ4MlIAbV9Zg6xwT8jlYdlRIyBTHmO3yM7kQ==}
peerDependencies:
'@stripe/stripe-js': '>=1.44.1 <8.0.0'
react: '>=16.8.0 <20.0.0'
react-dom: '>=16.8.0 <20.0.0'
'@stripe/stripe-js@7.4.0':
resolution: {integrity: sha512-lQHQPfXPTBeh0XFjq6PqSBAyR7umwcJbvJhXV77uGCUDD6ymXJU/f2164ydLMLCCceNuPlbV9b+1smx98efwWQ==}
engines: {node: '>=12.16'}
'@supabase/auth-js@2.69.1':
resolution: {integrity: sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==}
'@supabase/auth-js@2.70.0':
resolution: {integrity: sha512-BaAK/tOAZFJtzF1sE3gJ2FwTjLf4ky3PSvcvLGEgEmO4BSBkwWKu8l67rLLIBZPDnCyV7Owk2uPyKHa0kj5QGg==}
'@supabase/auth-ui-react@0.4.7':
resolution: {integrity: sha512-Lp4FQGFh7BMX1Y/BFaUKidbryL7eskj1fl6Lby7BeHrTctbdvDbCMjVKS8wZ2rxuI8FtPS2iU900fSb70FHknQ==}
@@ -2572,14 +2582,14 @@ packages:
'@supabase/postgrest-js@1.19.4':
resolution: {integrity: sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==}
'@supabase/realtime-js@2.11.10':
resolution: {integrity: sha512-SJKVa7EejnuyfImrbzx+HaD9i6T784khuw1zP+MBD7BmJYChegGxYigPzkKX8CK8nGuDntmeSD3fvriaH0EGZA==}
'@supabase/realtime-js@2.11.15':
resolution: {integrity: sha512-HQKRnwAqdVqJW/P9TjKVK+/ETpW4yQ8tyDPPtRMKOH4Uh3vQD74vmj353CYs8+YwVBKubeUOOEpI9CT8mT4obw==}
'@supabase/storage-js@2.7.1':
resolution: {integrity: sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==}
'@supabase/supabase-js@2.49.10':
resolution: {integrity: sha512-IRPcIdncuhD2m1eZ2Fkg0S1fq9SXlHfmAetBxPN66kVFtTucR8b01xKuVmKqcIJokB17umMf1bmqyS8yboXGsw==}
'@supabase/supabase-js@2.50.2':
resolution: {integrity: sha512-+27xlGgw7VyfwXXe+OiDJQosJNS+PPtjj1EnLR4uk+PKKZ91RA0/8NbIQybe6AGPanAaPtgOFFMMCArC6fZ++Q==}
'@surma/rollup-plugin-off-main-thread@2.2.3':
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
@@ -4333,6 +4343,11 @@ packages:
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
engines: {node: '>=0.10.0'}
isows@1.0.7:
resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==}
peerDependencies:
ws: '*'
iterator.prototype@1.1.3:
resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==}
engines: {node: '>= 0.4'}
@@ -9381,24 +9396,31 @@ snapshots:
'@stitches/core@1.2.8': {}
'@stripe/react-stripe-js@3.7.0(@stripe/stripe-js@7.4.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@stripe/stripe-js': 7.4.0
prop-types: 15.8.1
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
'@stripe/stripe-js@7.4.0': {}
'@supabase/auth-js@2.69.1':
'@supabase/auth-js@2.70.0':
dependencies:
'@supabase/node-fetch': 2.6.15
'@supabase/auth-ui-react@0.4.7(@supabase/supabase-js@2.49.10)':
'@supabase/auth-ui-react@0.4.7(@supabase/supabase-js@2.50.2)':
dependencies:
'@stitches/core': 1.2.8
'@supabase/auth-ui-shared': 0.1.8(@supabase/supabase-js@2.49.10)
'@supabase/supabase-js': 2.49.10
'@supabase/auth-ui-shared': 0.1.8(@supabase/supabase-js@2.50.2)
'@supabase/supabase-js': 2.50.2
prop-types: 15.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@supabase/auth-ui-shared@0.1.8(@supabase/supabase-js@2.49.10)':
'@supabase/auth-ui-shared@0.1.8(@supabase/supabase-js@2.50.2)':
dependencies:
'@supabase/supabase-js': 2.49.10
'@supabase/supabase-js': 2.50.2
'@supabase/functions-js@2.4.4':
dependencies:
@@ -9412,11 +9434,12 @@ snapshots:
dependencies:
'@supabase/node-fetch': 2.6.15
'@supabase/realtime-js@2.11.10':
'@supabase/realtime-js@2.11.15':
dependencies:
'@supabase/node-fetch': 2.6.15
'@types/phoenix': 1.6.6
'@types/ws': 8.18.1
isows: 1.0.7(ws@8.18.2)
ws: 8.18.2
transitivePeerDependencies:
- bufferutil
@@ -9426,13 +9449,13 @@ snapshots:
dependencies:
'@supabase/node-fetch': 2.6.15
'@supabase/supabase-js@2.49.10':
'@supabase/supabase-js@2.50.2':
dependencies:
'@supabase/auth-js': 2.69.1
'@supabase/auth-js': 2.70.0
'@supabase/functions-js': 2.4.4
'@supabase/node-fetch': 2.6.15
'@supabase/postgrest-js': 1.19.4
'@supabase/realtime-js': 2.11.10
'@supabase/realtime-js': 2.11.15
'@supabase/storage-js': 2.7.1
transitivePeerDependencies:
- bufferutil
@@ -11410,6 +11433,10 @@ snapshots:
isobject@3.0.1: {}
isows@1.0.7(ws@8.18.2):
dependencies:
ws: 8.18.2
iterator.prototype@1.1.3:
dependencies:
define-properties: 1.2.1