feat: homelab deployment - NPM + IONOS DNS + monitoring + NAS backup
- Restructured docker-compose for Nginx Proxy Manager (no custom nginx) - Added domain wordly.art configuration - Added Prometheus + Grafana monitoring stack with pre-configured dashboards - Added PostgreSQL backup script to NAS (daily/weekly/monthly rotation) - Added alert rules for backend, system, and Docker metrics - Updated deployment guide for NPM + IONOS DNS homelab setup - Added marketing plan document - PDF translator and watermark support - Enhanced middleware, routes, and translator modules Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import type {
|
||||
AvailableProvider,
|
||||
} from './types';
|
||||
import { API_BASE } from '@/lib/config';
|
||||
import { useTranslationStore } from '@/lib/store';
|
||||
|
||||
/** Fallback when API fails — Google is always available server-side */
|
||||
const FALLBACK_PROVIDERS: AvailableProvider[] = [
|
||||
@@ -59,8 +60,9 @@ const FALLBACK_LANGUAGES: Language[] = [
|
||||
];
|
||||
|
||||
export function useTranslationConfig(hasFile: boolean): UseTranslationConfigReturn {
|
||||
const { settings } = useTranslationStore();
|
||||
const [sourceLang, setSourceLang] = useState('auto');
|
||||
const [targetLang, setTargetLang] = useState('');
|
||||
const [targetLang, setTargetLang] = useState(settings.defaultTargetLanguage || '');
|
||||
const [provider, setProvider] = useState<Provider | null>(null);
|
||||
const [availableProviders, setAvailableProviders] = useState<AvailableProvider[]>([]);
|
||||
const [isLoadingProviders, setIsLoadingProviders] = useState(false);
|
||||
@@ -69,6 +71,13 @@ export function useTranslationConfig(hasFile: boolean): UseTranslationConfigRetu
|
||||
const [isLoadingLanguages, setIsLoadingLanguages] = useState(false);
|
||||
const [languagesError, setLanguagesError] = useState<string | null>(null);
|
||||
|
||||
// Sync with store default target language
|
||||
useEffect(() => {
|
||||
if (settings.defaultTargetLanguage && !targetLang) {
|
||||
setTargetLang(settings.defaultTargetLanguage);
|
||||
}
|
||||
}, [settings.defaultTargetLanguage]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Fetch available (admin-configured) providers
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
@@ -105,6 +114,16 @@ export function useTranslationConfig(hasFile: boolean): UseTranslationConfigRetu
|
||||
return () => { controller.abort(); clearTimeout(timeoutId); };
|
||||
}, []);
|
||||
|
||||
// Auto-select first classic provider for non-Pro users
|
||||
useEffect(() => {
|
||||
if (isLoadingProviders) return;
|
||||
if (provider !== null) return;
|
||||
if (isPro) return;
|
||||
if (availableProviders.length === 0) return;
|
||||
const firstClassic = availableProviders.find((p) => p.mode === 'classic');
|
||||
if (firstClassic) setProvider(firstClassic.id);
|
||||
}, [availableProviders, isLoadingProviders, isPro, provider]);
|
||||
|
||||
// Fetch supported languages
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
@@ -148,11 +167,12 @@ export function useTranslationConfig(hasFile: boolean): UseTranslationConfigRetu
|
||||
// Check user tier
|
||||
useEffect(() => {
|
||||
const checkTier = async () => {
|
||||
const isProTier = (u: any) => ['pro', 'business', 'enterprise'].includes(u?.plan ?? u?.tier ?? '');
|
||||
const userStr = localStorage.getItem('user');
|
||||
if (userStr) {
|
||||
try {
|
||||
const user = JSON.parse(userStr);
|
||||
if (user.tier) { setIsPro(user.tier === 'pro'); return; }
|
||||
if (user?.plan || user?.tier) { setIsPro(isProTier(user)); return; }
|
||||
} catch { /* continue */ }
|
||||
}
|
||||
try {
|
||||
@@ -164,7 +184,7 @@ export function useTranslationConfig(hasFile: boolean): UseTranslationConfigRetu
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
const user = result.data;
|
||||
setIsPro(user.tier === 'pro');
|
||||
setIsPro(isProTier(user));
|
||||
localStorage.setItem('user', JSON.stringify(user));
|
||||
} else {
|
||||
setIsPro(false);
|
||||
|
||||
Reference in New Issue
Block a user