From 20759894095524629f3d445368bbe115e387e952 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Thu, 5 Mar 2026 19:37:51 +0530 Subject: [PATCH] feat(32-03): create JKVideoButton with colocated loading state - Extracted video button from JKSessionScreen - Local loading state prevents parent re-renders - memo() wrapper for render optimization - Preserves 10-second loading timeout behavior - Handles permission checks via canVideo prop --- jam-ui/src/components/client/JKVideoButton.js | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 jam-ui/src/components/client/JKVideoButton.js diff --git a/jam-ui/src/components/client/JKVideoButton.js b/jam-ui/src/components/client/JKVideoButton.js new file mode 100644 index 000000000..276881504 --- /dev/null +++ b/jam-ui/src/components/client/JKVideoButton.js @@ -0,0 +1,78 @@ +import React, { useState, useCallback, memo } from 'react'; +import { Button, Spinner } from 'reactstrap'; +import { toast } from 'react-toastify'; +import PropTypes from 'prop-types'; +import videoIcon from '../../assets/images/icons8-video-call-50.png'; + +/** + * Self-contained video button with colocated loading state. + * Loading state changes only re-render this component, not the parent. + * + * State colocation: https://kentcdodds.com/blog/state-colocation-will-make-your-react-app-faster + */ +const JKVideoButton = memo(({ + canVideo, + getVideoUrl, + onUpgradePrompt, + className +}) => { + const [loading, setLoading] = useState(false); + + // Open external link in new window/tab + const openExternalLink = useCallback((url) => { + window.open(url, '_blank', 'noopener,noreferrer'); + }, []); + + const handleClick = useCallback(async () => { + if (!canVideo()) { + onUpgradePrompt(); + return; + } + + try { + setLoading(true); + + // Get video conferencing room URL from server + const response = await getVideoUrl(); + const videoUrl = `${response.url}&audiooff=true`; + + // Open video URL in new browser window/tab + openExternalLink(videoUrl); + + } catch (error) { + toast.error('Failed to start video session'); + } finally { + // Keep loading state for 10 seconds to prevent multiple clicks + setTimeout(() => setLoading(false), 10000); + } + }, [canVideo, getVideoUrl, onUpgradePrompt, openExternalLink]); + + return ( + + ); +}); + +JKVideoButton.displayName = 'JKVideoButton'; + +JKVideoButton.propTypes = { + canVideo: PropTypes.func.isRequired, + getVideoUrl: PropTypes.func.isRequired, + onUpgradePrompt: PropTypes.func.isRequired, + className: PropTypes.string +}; + +export default JKVideoButton;