From aa1f62740298d4345fc41f3536a4d51f4595c898 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sat, 31 Jan 2026 19:01:34 -0600 Subject: [PATCH] Enhancement: Refactored `copyToClipboard` utility function to include notification handling internally, eliminating duplicate notification code across the frontend. The function now accepts optional parameters for customizing success/failure messages while providing consistent behavior across all copy operations. --- CHANGELOG.md | 1 + frontend/src/components/SeriesModal.jsx | 10 ++-- frontend/src/components/Sidebar.jsx | 14 ++--- frontend/src/components/VODModal.jsx | 10 ++-- .../components/tables/ChannelTableStreams.jsx | 10 ++-- .../src/components/tables/ChannelsTable.jsx | 38 ++++---------- frontend/src/utils.js | 52 ++++++++++++++----- 7 files changed, 61 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f48d9456..152dd5b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Better auto-generation of request/response schemas - Improved documentation accuracy with serializer introspection - Switched to uv for package management: Migrated from pip to uv (Astral's fast Python package installer) for improved dependency resolution speed and reliability. This includes updates to Docker build processes, installation scripts (debian_install.sh), and project configuration (pyproject.toml) to leverage uv's features like virtual environment management and lockfile generation. - Thanks [@tobimichael96](https://github.com/tobimichael96) for getting it started! +- Copy to Clipboard: Refactored `copyToClipboard` utility function to include notification handling internally, eliminating duplicate notification code across the frontend. The function now accepts optional parameters for customizing success/failure messages while providing consistent behavior across all copy operations. ### Fixed diff --git a/frontend/src/components/SeriesModal.jsx b/frontend/src/components/SeriesModal.jsx index 05023712..c8e551af 100644 --- a/frontend/src/components/SeriesModal.jsx +++ b/frontend/src/components/SeriesModal.jsx @@ -287,13 +287,9 @@ const SeriesModal = ({ series, opened, onClose }) => { const handleCopyEpisodeLink = async (episode) => { const streamUrl = getEpisodeStreamUrl(episode); - const success = await copyToClipboard(streamUrl); - notifications.show({ - title: success ? 'Link Copied!' : 'Copy Failed', - message: success - ? 'Episode link copied to clipboard' - : 'Failed to copy link to clipboard', - color: success ? 'green' : 'red', + await copyToClipboard(streamUrl, { + successTitle: 'Link Copied!', + successMessage: 'Episode link copied to clipboard', }); }; diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index a25aa301..435509e0 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -144,16 +144,10 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => { // No need to fetch them again here - just use the store values const copyPublicIP = async () => { - const success = await copyToClipboard(environment.public_ip); - if (success) { - notifications.show({ - title: 'Success', - message: 'Public IP copied to clipboard', - color: 'green', - }); - } else { - console.error('Failed to copy public IP to clipboard'); - } + await copyToClipboard(environment.public_ip, { + successTitle: 'Success', + successMessage: 'Public IP copied to clipboard', + }); }; const onLogout = async () => { diff --git a/frontend/src/components/VODModal.jsx b/frontend/src/components/VODModal.jsx index 7df90ec0..f0ff7fec 100644 --- a/frontend/src/components/VODModal.jsx +++ b/frontend/src/components/VODModal.jsx @@ -268,13 +268,9 @@ const VODModal = ({ vod, opened, onClose }) => { const handleCopyLink = async () => { const streamUrl = getStreamUrl(); if (!streamUrl) return; - const success = await copyToClipboard(streamUrl); - notifications.show({ - title: success ? 'Link Copied!' : 'Copy Failed', - message: success - ? 'Stream link copied to clipboard' - : 'Failed to copy link to clipboard', - color: success ? 'green' : 'red', + await copyToClipboard(streamUrl, { + successTitle: 'Link Copied!', + successMessage: 'Stream link copied to clipboard', }); }; diff --git a/frontend/src/components/tables/ChannelTableStreams.jsx b/frontend/src/components/tables/ChannelTableStreams.jsx index 0443b3ca..30dabf3d 100644 --- a/frontend/src/components/tables/ChannelTableStreams.jsx +++ b/frontend/src/components/tables/ChannelTableStreams.jsx @@ -379,13 +379,9 @@ const ChannelStreams = ({ channel, isExpanded }) => { style={{ cursor: 'pointer' }} onClick={async (e) => { e.stopPropagation(); - const success = await copyToClipboard(stream.url); - notifications.show({ - title: success ? 'URL Copied' : 'Copy Failed', - message: success - ? 'Stream URL copied to clipboard' - : 'Failed to copy URL to clipboard', - color: success ? 'green' : 'red', + await copyToClipboard(stream.url, { + successTitle: 'URL Copied', + successMessage: 'Stream URL copied to clipboard', }); }} > diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx index f41ca72d..371cb77f 100644 --- a/frontend/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -727,14 +727,6 @@ const ChannelsTable = ({ onReady }) => { setRecordingModalOpen(false); }; - const handleCopy = async (textToCopy, ref) => { - const success = await copyToClipboard(textToCopy); - notifications.show({ - title: success ? 'Copied!' : 'Copy Failed', - message: success ? undefined : 'Failed to copy to clipboard', - color: success ? 'green' : 'red', - }); - }; // Build URLs with parameters const buildM3UUrl = () => { const params = new URLSearchParams(); @@ -759,35 +751,23 @@ const ChannelsTable = ({ onReady }) => { }; // Example copy URLs const copyM3UUrl = async () => { - const success = await copyToClipboard(buildM3UUrl()); - notifications.show({ - title: success ? 'M3U URL Copied!' : 'Copy Failed', - message: success - ? 'The M3U URL has been copied to your clipboard.' - : 'Failed to copy M3U URL to clipboard', - color: success ? 'green' : 'red', + await copyToClipboard(buildM3UUrl(), { + successTitle: 'M3U URL Copied!', + successMessage: 'The M3U URL has been copied to your clipboard.', }); }; const copyEPGUrl = async () => { - const success = await copyToClipboard(buildEPGUrl()); - notifications.show({ - title: success ? 'EPG URL Copied!' : 'Copy Failed', - message: success - ? 'The EPG URL has been copied to your clipboard.' - : 'Failed to copy EPG URL to clipboard', - color: success ? 'green' : 'red', + await copyToClipboard(buildEPGUrl(), { + successTitle: 'EPG URL Copied!', + successMessage: 'The EPG URL has been copied to your clipboard.', }); }; const copyHDHRUrl = async () => { - const success = await copyToClipboard(hdhrUrl); - notifications.show({ - title: success ? 'HDHR URL Copied!' : 'Copy Failed', - message: success - ? 'The HDHR URL has been copied to your clipboard.' - : 'Failed to copy HDHR URL to clipboard', - color: success ? 'green' : 'red', + await copyToClipboard(hdhrUrl, { + successTitle: 'HDHR URL Copied!', + successMessage: 'The HDHR URL has been copied to your clipboard.', }); }; diff --git a/frontend/src/utils.js b/frontend/src/utils.js index bb1b6060..27abf86c 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; +import { notifications } from '@mantine/notifications'; export default { Limiter: (n, list) => { @@ -76,30 +77,53 @@ export function sleep(ms) { export const getDescendantProp = (obj, path) => path.split('.').reduce((acc, part) => acc && acc[part], obj); -export const copyToClipboard = async (value) => { +export const copyToClipboard = async (value, options = {}) => { + const { + successTitle = 'Copied!', + successMessage = 'Copied to clipboard', + failureTitle = 'Copy Failed', + failureMessage = 'Failed to copy to clipboard', + showNotification = true, + } = options; + + let success = false; + if (navigator.clipboard) { // Modern method, using navigator.clipboard try { await navigator.clipboard.writeText(value); - return true; + success = true; } catch (err) { console.error('Failed to copy: ', err); } } - // Fallback method for environments without clipboard support - try { - const textarea = document.createElement('textarea'); - textarea.value = value; - document.body.appendChild(textarea); - textarea.select(); - const successful = document.execCommand('copy'); - document.body.removeChild(textarea); - return successful; - } catch (err) { - console.error('Failed to copy with fallback method: ', err); - return false; + if (!success) { + // Fallback method for environments without clipboard support + try { + const textarea = document.createElement('textarea'); + textarea.value = value; + document.body.appendChild(textarea); + textarea.select(); + const successful = document.execCommand('copy'); + document.body.removeChild(textarea); + success = successful; + } catch (err) { + console.error('Failed to copy with fallback method: ', err); + success = false; + } } + + // Show notification if enabled + if (showNotification) { + notifications.show({ + title: success ? successTitle : failureTitle, + message: success ? successMessage : failureMessage, + color: success ? 'green' : 'red', + }); + } + + return success; }; export const setCustomProperty = (input, key, value, serialize = false) => {