diff --git a/js/app/components/live-dashboard/bento-grid.tsx b/js/app/components/live-dashboard/bento-grid.tsx index 3e512dc5f..6b8e025c1 100644 --- a/js/app/components/live-dashboard/bento-grid.tsx +++ b/js/app/components/live-dashboard/bento-grid.tsx @@ -9,8 +9,11 @@ import { useSegmentTiming, zero, } from "@streamplace/components"; -import { ProblemsWrapper } from "@streamplace/components/src/components/dashboard/problems"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { + ProblemsWrapper, + ProblemsWrapperRef, +} from "@streamplace/components/src/components/dashboard/problems"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Dimensions, Platform, ScrollView, View } from "react-native"; import LivestreamPanel from "./livestream-panel"; import StreamMonitor from "./stream-monitor"; @@ -18,18 +21,18 @@ import StreamMonitor from "./stream-monitor"; const { flex, p, gap, layout, bg } = zero; interface BentoGridProps { - userProfile: any; isLive: boolean; videoRef: any; } -export default function BentoGrid({ - userProfile, - isLive, - videoRef, -}: BentoGridProps) { +export default function BentoGrid({ isLive, videoRef }: BentoGridProps) { const navigation = useNavigation(); const isWeb = Platform.OS === "web"; + const problemsRef = useRef(null); + + const handleProblemsPress = useCallback(() => { + problemsRef.current?.setDismiss(false); + }, []); // Screen width state for responsive design const [screenWidth, setScreenWidth] = useState( @@ -63,6 +66,7 @@ export default function BentoGrid({ const profile = useProfile(); const viewers = useLivestreamStore((x) => x.viewers); const chat = useLivestreamStore((x) => x.chat); + const problems = useLivestreamStore((x) => x.problems); const segmentTiming = useSegmentTiming(); const seg = useSegment(); const ingestConnectionState = usePlayerStore((x) => x.ingestConnectionState); @@ -131,7 +135,7 @@ export default function BentoGrid({ if (isDesktop) { // Desktop layout (>= 1200px) - Original bento grid return ( - + @@ -201,7 +207,7 @@ export default function BentoGrid({ } return ( - + {/* Header always at top */} @@ -215,6 +221,8 @@ export default function BentoGrid({ bitrate={getBitrate()} timeBetweenSegments={segmentTiming.timeBetweenSegments || 0} connectionStatus={getConnectionStatus} + problemsCount={problems.length} + onProblemsPress={handleProblemsPress} /> diff --git a/js/app/src/screens/live-dashboard.tsx b/js/app/src/screens/live-dashboard.tsx index 373d659bc..84db86bb9 100644 --- a/js/app/src/screens/live-dashboard.tsx +++ b/js/app/src/screens/live-dashboard.tsx @@ -45,11 +45,7 @@ export default function LiveDashboard() { - + diff --git a/js/components/src/components/dashboard/header.tsx b/js/components/src/components/dashboard/header.tsx index 4a25882d6..68758586e 100644 --- a/js/components/src/components/dashboard/header.tsx +++ b/js/components/src/components/dashboard/header.tsx @@ -1,5 +1,5 @@ -import { Car, Radio, Users } from "lucide-react-native"; -import { Text, View } from "react-native"; +import { AlertCircle, Car, Radio, Users } from "lucide-react-native"; +import { Pressable, Text, View } from "react-native"; import * as zero from "../../ui"; const { bg, r, borders, px, py, text, layout, gap } = zero; @@ -103,6 +103,8 @@ interface HeaderProps { bitrate?: string; timeBetweenSegments?: number; connectionStatus?: "excellent" | "good" | "poor" | "offline"; + problemsCount?: number; + onProblemsPress?: () => void; } export default function Header({ @@ -113,6 +115,8 @@ export default function Header({ bitrate = "0 mbps", timeBetweenSegments = 0, connectionStatus = "offline", + problemsCount = 0, + onProblemsPress, }: HeaderProps) { const getConnectionQuality = (): "good" | "warning" | "error" => { if (timeBetweenSegments <= 1500) return "good"; @@ -139,7 +143,37 @@ export default function Header({ {streamTitle} - + + + {problemsCount > 0 && ( + + + + + {problemsCount} {problemsCount === 1 ? "Issue" : "Issues"} + + + + )} + diff --git a/js/components/src/components/dashboard/index.tsx b/js/components/src/components/dashboard/index.tsx index 49c1f920a..81497d634 100644 --- a/js/components/src/components/dashboard/index.tsx +++ b/js/components/src/components/dashboard/index.tsx @@ -2,4 +2,4 @@ export { default as ChatPanel } from "./chat-panel"; export { default as Header } from "./header"; export { default as InformationWidget } from "./information-widget"; export { default as ModActions } from "./mod-actions"; -export { default as Problems } from "./problems"; +export { default as Problems, ProblemsWrapperRef } from "./problems"; diff --git a/js/components/src/components/dashboard/problems.tsx b/js/components/src/components/dashboard/problems.tsx index 284686a52..76309ddcc 100644 --- a/js/components/src/components/dashboard/problems.tsx +++ b/js/components/src/components/dashboard/problems.tsx @@ -5,7 +5,7 @@ import { Info, Sparkle, } from "lucide-react-native"; -import { useState } from "react"; +import { forwardRef, useImperativeHandle, useState } from "react"; import { Linking, Pressable, View } from "react-native"; import { useLivestreamStore } from "../../livestream-store"; import { LivestreamProblem } from "../../livestream-store/livestream-state"; @@ -104,14 +104,23 @@ const Problems = ({ ); }; -export const ProblemsWrapper = ({ - children, -}: { - children: React.ReactElement; -}) => { +export interface ProblemsWrapperRef { + setDismiss: (value: boolean) => void; +} + +export const ProblemsWrapper = forwardRef< + ProblemsWrapperRef, + { + children: React.ReactElement; + } +>(({ children }, ref) => { const problems = useLivestreamStore((x) => x.problems); const [dismiss, setDismiss] = useState(false); + useImperativeHandle(ref, () => ({ + setDismiss, + })); + return ( <> {children} @@ -148,6 +157,6 @@ export const ProblemsWrapper = ({ )} ); -}; +}); export default Problems;