'use client'; import { useState, useEffect } from 'react'; import { settingsStore } from '@/lib/store/settings-store'; import { useSubscriptionSync } from '@/lib/hooks/useSubscriptionSync'; import { Lock } from 'lucide-react'; const SESSION_UNLOCKED_KEY = 'kvideo-unlocked'; export function PasswordGate({ children, hasEnvPassword: initialHasEnvPassword }: { children: React.ReactNode, hasEnvPassword: boolean }) { // Enable background subscription syncing globally useSubscriptionSync(); const [isLocked, setIsLocked] = useState(true); const [password, setPassword] = useState(''); const [error, setError] = useState(false); const [isClient, setIsClient] = useState(false); const [hasEnvPassword, setHasEnvPassword] = useState(initialHasEnvPassword); const [isValidating, setIsValidating] = useState(false); useEffect(() => { let mounted = true; const init = async () => { const settings = settingsStore.getSettings(); const isUnlocked = sessionStorage.getItem(SESSION_UNLOCKED_KEY) === 'true'; // 1. Initial local check (fast) const localLocked = (settings.passwordAccess || initialHasEnvPassword) && !isUnlocked; if (mounted) setIsLocked(localLocked); if (mounted) setIsClient(true); // 2. Fetch remote config & sync try { const res = await fetch('/api/config'); if (!res.ok) throw new Error('Failed to fetch config'); const data = await res.json(); if (mounted) { setHasEnvPassword(data.hasEnvPassword); // CRITICAL: Sync subscriptions immediately if (data.subscriptionSources) { console.log('Syncing env subscriptions:', data.subscriptionSources); settingsStore.syncEnvSubscriptions(data.subscriptionSources); } // Re-evaluate lock status with confirmed server state // We only care about envPassword if we are not unlocked. // Access control logic: // Locked IF: (Local setting ON OR Env Password Exists) AND (Not Unlocked) const confirmLocked = (settings.passwordAccess || data.hasEnvPassword) && !isUnlocked; setIsLocked(confirmLocked); } } catch (e) { console.error("PasswordGate init failed:", e); // Fallback: rely on initial/local state which was already set } }; init(); return () => { mounted = false; }; }, [initialHasEnvPassword]); // Subscribe to settings changes (real-time updates) useEffect(() => { // Function to handle updates from the store const handleSettingsUpdate = () => { const settings = settingsStore.getSettings(); const isUnlocked = sessionStorage.getItem(SESSION_UNLOCKED_KEY) === 'true'; // We can't easily check env password synchronously here, but we can check local settings. // If local setting says lock, and we are not unlocked, we lock. // If local setting says unlock (and no env password known yet), we unlock. // To be safe, we might just re-run checkLockStatus() but that's async. // For immediate UI feedback on "Enable/Disable Password" toggle in settings: // If password access is disabled in settings, and we assume no env password for a moment (or rely on previous state): if (!settings.passwordAccess && !hasEnvPassword) { setIsLocked(false); } else if (settings.passwordAccess && !isUnlocked) { setIsLocked(true); } }; const unsubscribe = settingsStore.subscribe(handleSettingsUpdate); return () => unsubscribe(); }, [hasEnvPassword]); const handleUnlock = async (e: React.FormEvent) => { e.preventDefault(); setIsValidating(true); const settings = settingsStore.getSettings(); // First check local passwords if (settings.accessPasswords.includes(password)) { sessionStorage.setItem(SESSION_UNLOCKED_KEY, 'true'); setIsLocked(false); setError(false); setIsValidating(false); return; } // Then check env password via API if (hasEnvPassword) { try { const res = await fetch('/api/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }), }); const data = await res.json(); if (data.valid) { sessionStorage.setItem(SESSION_UNLOCKED_KEY, 'true'); setIsLocked(false); setError(false); setIsValidating(false); return; } } catch { // API error, proceed to show error } } // Password didn't match setError(true); setIsValidating(false); const form = document.getElementById('password-form'); form?.classList.add('animate-shake'); setTimeout(() => form?.classList.remove('animate-shake'), 500); }; if (!isClient) return null; // Prevent hydration mismatch if (!isLocked) { return <>{children}>; } return (