'use client'; import { useState, useEffect } from 'react'; import { settingsStore } from '@/lib/store/settings-store'; import { Lock } from 'lucide-react'; const SESSION_UNLOCKED_KEY = 'kvideo-unlocked'; export function PasswordGate({ children, hasEnvPassword: initialHasEnvPassword }: { children: React.ReactNode, hasEnvPassword: boolean }) { 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(() => { const settings = settingsStore.getSettings(); const isUnlocked = sessionStorage.getItem(SESSION_UNLOCKED_KEY) === 'true'; // Determine initial lock state immediately // Lock if (local password enabled OR env password exists) AND not already unlocked in session const shouldBeLocked = (settings.passwordAccess || initialHasEnvPassword) && !isUnlocked; setIsLocked(shouldBeLocked); setIsClient(true); // Still run background checks to keep everything in sync checkEnvPasswordStatus(); checkLockStatus(); }, [initialHasEnvPassword]); const checkEnvPasswordStatus = async () => { try { const res = await fetch('/api/config'); const data = await res.json(); setHasEnvPassword(data.hasEnvPassword); // Sync subscription sources if provided by environment if (data.subscriptionSources) { settingsStore.syncEnvSubscriptions(data.subscriptionSources); } } catch { // Silently fail } }; const checkLockStatus = async () => { const settings = settingsStore.getSettings(); const isUnlocked = sessionStorage.getItem(SESSION_UNLOCKED_KEY) === 'true'; try { const res = await fetch('/api/config'); const data = await res.json(); const envPasswordSet = data.hasEnvPassword; // Updated lock state check const currentlyLocked = (settings.passwordAccess || envPasswordSet) && !isUnlocked; setIsLocked(currentlyLocked); } catch { // Handle error by checking local settings const currentlyLocked = settings.passwordAccess && !isUnlocked; setIsLocked(currentlyLocked); } }; 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 (