diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx
index 8729fc9..6a303d2 100644
--- a/components/layout/Navbar.tsx
+++ b/components/layout/Navbar.tsx
@@ -6,7 +6,7 @@ import Image from 'next/image';
import { ThemeSwitcher } from '@/components/ThemeSwitcher';
import { Icons } from '@/components/ui/Icon';
import { siteConfig } from '@/lib/config/site-config';
-import { getSession, clearSession, type AuthSession } from '@/lib/store/auth-store';
+import { getSession, clearSession, hasPermission, type AuthSession } from '@/lib/store/auth-store';
import { LogOut } from 'lucide-react';
interface NavbarProps {
@@ -24,7 +24,8 @@ export function Navbar({ onReset, isPremiumMode = false }: NavbarProps) {
const handleLogout = () => {
clearSession();
- window.location.reload();
+ // Navigate to root to clear search query params
+ window.location.href = '/';
};
return (
@@ -59,7 +60,8 @@ export function Navbar({ onReset, isPremiumMode = false }: NavbarProps) {
- {/* IPTV Link */}
+ {/* IPTV Link - only show if user has iptv_access or no auth configured */}
+ {hasPermission('iptv_access') && (
+ )}
{/* User Info */}
{session && (
diff --git a/components/player/EpisodeList.tsx b/components/player/EpisodeList.tsx
index 7cd8ef8..cdefba5 100644
--- a/components/player/EpisodeList.tsx
+++ b/components/player/EpisodeList.tsx
@@ -74,12 +74,46 @@ export function EpisodeList({
useEffect(() => {
if (!sources) return;
const initial: Record = {};
+ let hasMissing = false;
sources.forEach(s => {
if (s.latency !== undefined) {
initial[s.source] = s.latency;
+ } else {
+ hasMissing = true;
}
});
setLatencies(initial);
+
+ // Auto-refresh latencies for sources that don't have them
+ if (hasMissing && sources.length > 1) {
+ const autoRefresh = async () => {
+ const missing = sources.filter(s => s.latency === undefined);
+ const results = await Promise.all(
+ missing.map(async (source) => {
+ try {
+ const response = await fetch('/api/ping', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ url: source.source }),
+ });
+ if (response.ok) {
+ const data = await response.json();
+ return { source: source.source, latency: data.latency as number | undefined };
+ }
+ } catch { /* ignore */ }
+ return { source: source.source, latency: undefined };
+ })
+ );
+ setLatencies(prev => {
+ const updated = { ...prev };
+ results.forEach(({ source, latency }) => {
+ if (latency !== undefined) updated[source] = latency;
+ });
+ return updated;
+ });
+ };
+ autoRefresh();
+ }
}, [sources]);
// Refresh latencies
diff --git a/components/search/SourceBadgeList.tsx b/components/search/SourceBadgeList.tsx
index e328e7c..5b507e4 100644
--- a/components/search/SourceBadgeList.tsx
+++ b/components/search/SourceBadgeList.tsx
@@ -11,6 +11,8 @@ import { Icons } from '@/components/ui/Icon';
import { SourceBadgeItem } from './SourceBadgeItem';
import { useKeyboardNavigation } from '@/lib/hooks/useKeyboardNavigation';
+const EXPAND_KEY = 'kvideo_source_badges_expanded';
+
interface Source {
id: string;
name: string;
@@ -24,13 +26,25 @@ interface SourceBadgeListProps {
}
export function SourceBadgeList({ sources, selectedSources, onToggleSource }: SourceBadgeListProps) {
- const [isExpanded, setIsExpanded] = useState(false);
+ const [isExpanded, setIsExpanded] = useState(() => {
+ if (typeof window === 'undefined') return true;
+ const saved = localStorage.getItem(EXPAND_KEY);
+ return saved !== 'false'; // default to expanded
+ });
const [focusedIndex, setFocusedIndex] = useState(-1);
const [hasOverflow, setHasOverflow] = useState(false);
const containerRef = useRef(null);
const badgeContainerRef = useRef(null);
const badgeRefs = useRef<(HTMLButtonElement | null)[]>([]);
+ const toggleExpanded = useCallback(() => {
+ setIsExpanded(prev => {
+ const next = !prev;
+ localStorage.setItem(EXPAND_KEY, String(next));
+ return next;
+ });
+ }, []);
+
// Keyboard navigation
useKeyboardNavigation({
enabled: true,
@@ -101,7 +115,7 @@ export function SourceBadgeList({ sources, selectedSources, onToggleSource }: So
{hasOverflow && (