From a0d02a843e8efa15090c56052d8113fcd3e069f4 Mon Sep 17 00:00:00 2001 From: Piyush Gupta <56182734+gupta-piyush19@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:17:21 +0530 Subject: [PATCH] fix: video embed url (#4485) Co-authored-by: Dhruwang --- .../file-input/components/video-settings.tsx | 51 +++++++++--------- packages/lib/messages/de-DE.json | 3 ++ packages/lib/messages/en-US.json | 3 ++ packages/lib/messages/fr-FR.json | 3 ++ packages/lib/messages/pt-BR.json | 3 ++ packages/lib/utils/videoUpload.ts | 52 +++++++------------ .../src/components/general/question-media.tsx | 4 +- 7 files changed, 59 insertions(+), 60 deletions(-) diff --git a/apps/web/modules/ui/components/file-input/components/video-settings.tsx b/apps/web/modules/ui/components/file-input/components/video-settings.tsx index 6167c03da7..afcce8b415 100644 --- a/apps/web/modules/ui/components/file-input/components/video-settings.tsx +++ b/apps/web/modules/ui/components/file-input/components/video-settings.tsx @@ -2,10 +2,10 @@ import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-to import { Button } from "@/modules/ui/components/button"; import { Input } from "@/modules/ui/components/input"; import { AlertTriangle } from "lucide-react"; +import { useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "react-hot-toast"; -import { checkForYoutubeUrl } from "@formbricks/lib/utils/videoUpload"; -import { extractYoutubeId, parseVideoUrl } from "@formbricks/lib/utils/videoUpload"; +import { checkForYoutubeUrl, convertToEmbedUrl, extractYoutubeId } from "@formbricks/lib/utils/videoUpload"; import { Label } from "../../label"; import { checkForYoutubePrivacyMode } from "../lib/utils"; @@ -24,6 +24,7 @@ export const VideoSettings = ({ videoUrl, setVideoUrlTemp, }: VideoSettingsProps) => { + const t = useTranslations(); const [isYoutubeLink, setIsYoutubeLink] = useState(checkForYoutubeUrl(uploadedVideoUrl)); const [isYoutubePrivacyModeEnabled, setIsYoutubePrivacyModeEnabled] = useState( checkForYoutubePrivacyMode(uploadedVideoUrl) @@ -34,7 +35,7 @@ export const VideoSettings = ({ const videoId = extractYoutubeId(uploadedVideoUrl); if (!videoId) { - toast.error("Invalid YouTube URL"); + toast.error(t("environments.surveys.edit.invalid_youtube_url")); return; } const newUrl = isYoutubePrivacyModeEnabled @@ -49,24 +50,26 @@ export const VideoSettings = ({ const handleAddVideo = (e: React.MouseEvent) => { e.preventDefault(); - const parsedUrl = parseVideoUrl(uploadedVideoUrl); - if (parsedUrl) { - setUploadedVideoUrl(parsedUrl); - onFileUpload([parsedUrl], "video"); + const embedUrl = convertToEmbedUrl(uploadedVideoUrl.trim()); + if (embedUrl) { + setUploadedVideoUrl(embedUrl); + onFileUpload([embedUrl], "video"); } else { - toast.error("Url not supported"); + toast.error(t("environments.surveys.edit.url_not_supported")); } }; const handleRemoveVideo = (e: React.MouseEvent) => { e.preventDefault(); setVideoUrlTemp(""); + setUploadedVideoUrl(""); onFileUpload([], "video"); }; const handleVideoUrlChange = (e: React.ChangeEvent) => { const videoUrl = e.target.value; setUploadedVideoUrl(videoUrl); + // Check if the URL is from one of the supported platforms const isSupportedPlatform = [ "youtube.com", @@ -82,26 +85,26 @@ export const VideoSettings = ({ }; const isAddButtonDisabled = () => { - return uploadedVideoUrl.trim() !== "" ? false : true; + return uploadedVideoUrl.trim() === ""; }; return ( -
- +
+
handleVideoUrlChange(e)} + onChange={handleVideoUrlChange} /> {uploadedVideoUrl && videoUrl === uploadedVideoUrl ? ( - ) : ( - )}
@@ -109,23 +112,19 @@ export const VideoSettings = ({ {showPlatformWarning && (
-

- Please enter a valid Youtube, Vimeo or Loom Url. We currently do not support other video hosting - providers. -

+

{t("environments.surveys.edit.invalid_video_url_warning")}

)} + {isYoutubeLink && ( { - toggleYoutubePrivacyMode(); - }} + onToggle={toggleYoutubePrivacyMode} title="YouTube Privacy Mode" description="Keeps user tracking to a minimum" childBorder={true}> )} - +
); }; diff --git a/packages/lib/messages/de-DE.json b/packages/lib/messages/de-DE.json index 2c22446374..063331335b 100644 --- a/packages/lib/messages/de-DE.json +++ b/packages/lib/messages/de-DE.json @@ -1504,6 +1504,8 @@ "input_color": "Farbe des Eingabefelds", "invalid_segment": "Ungültiges Segment", "invalid_targeting": "Ungültiges Targeting: Bitte überprüfe deine Zielgruppenfilter", + "invalid_video_url_warning": "Bitte gib eine gültige YouTube-, Vimeo- oder Loom-URL ein. Andere Video-Plattformen werden derzeit nicht unterstützt.", + "invalid_youtube_url": "Ungültige YouTube-URL", "is_after": "Ist nach", "is_before": "Ist vor", "is_booked": "Ist gebucht", @@ -1674,6 +1676,7 @@ "upper_label": "Oberes Label", "url_encryption": "URL-Verschlüsselung", "url_filters": "URL-Filter", + "url_not_supported": "URL nicht unterstützt", "use_with_caution": "Mit Vorsicht verwenden", "user_targeting_is_currently_only_available_when": "Benutzerzielgruppen sind derzeit nur verfügbar, wenn", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne es zuerst aus der Logik.", diff --git a/packages/lib/messages/en-US.json b/packages/lib/messages/en-US.json index 6d016f23ec..327569a56f 100644 --- a/packages/lib/messages/en-US.json +++ b/packages/lib/messages/en-US.json @@ -1504,6 +1504,8 @@ "input_color": "Input color", "invalid_segment": "Invalid segment", "invalid_targeting": "Invalid targeting: Please check your audience filters", + "invalid_video_url_warning": "Please enter a valid YouTube, Vimeo, or Loom URL. We currently do not support other video hosting providers.", + "invalid_youtube_url": "Invalid YouTube URL", "is_after": "Is after", "is_before": "Is before", "is_booked": "Is booked", @@ -1674,6 +1676,7 @@ "upper_label": "Upper Label", "url_encryption": "URL Encryption", "url_filters": "URL Filters", + "url_not_supported": "URL not supported", "use_with_caution": "Use with caution", "user_targeting_is_currently_only_available_when": "User targeting is currently only available when", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} is used in logic of question {questionIndex}. Please remove it from logic first.", diff --git a/packages/lib/messages/fr-FR.json b/packages/lib/messages/fr-FR.json index 3611c5a342..9457f98fa0 100644 --- a/packages/lib/messages/fr-FR.json +++ b/packages/lib/messages/fr-FR.json @@ -1504,6 +1504,8 @@ "input_color": "Couleur d'entrée", "invalid_segment": "Segment invalide", "invalid_targeting": "Ciblage invalide : Veuillez vérifier vos filtres d'audience", + "invalid_video_url_warning": "Merci d'entrer une URL YouTube, Vimeo ou Loom valide. Les autres plateformes vidéo ne sont pas encore supportées.", + "invalid_youtube_url": "URL YouTube invalide", "is_after": "est après", "is_before": "Est avant", "is_booked": "Est réservé", @@ -1674,6 +1676,7 @@ "upper_label": "Étiquette supérieure", "url_encryption": "Chiffrement d'URL", "url_filters": "Filtres d'URL", + "url_not_supported": "URL non supportée", "use_with_caution": "À utiliser avec précaution", "user_targeting_is_currently_only_available_when": "La ciblage des utilisateurs est actuellement disponible uniquement lorsque", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} est utilisé dans la logique de la question {questionIndex}. Veuillez d'abord le supprimer de la logique.", diff --git a/packages/lib/messages/pt-BR.json b/packages/lib/messages/pt-BR.json index b13d9e804a..d902ae57e7 100644 --- a/packages/lib/messages/pt-BR.json +++ b/packages/lib/messages/pt-BR.json @@ -1504,6 +1504,8 @@ "input_color": "Cor de entrada", "invalid_segment": "Segmento inválido", "invalid_targeting": "Segmentação inválida: Por favor, verifique os filtros do seu público", + "invalid_video_url_warning": "Por favor, insira uma URL válida do YouTube, Vimeo ou Loom. No momento, não suportamos outros provedores de vídeo.", + "invalid_youtube_url": "URL do YouTube inválida", "is_after": "é depois", "is_before": "é antes", "is_booked": "Tá reservado", @@ -1674,6 +1676,7 @@ "upper_label": "Etiqueta Superior", "url_encryption": "Criptografia de URL", "url_filters": "Filtros de URL", + "url_not_supported": "URL não suportada", "use_with_caution": "Use com cuidado", "user_targeting_is_currently_only_available_when": "A segmentação de usuários está disponível atualmente apenas quando", "variable_is_used_in_logic_of_question_please_remove_it_from_logic_first": "{variable} está sendo usado na lógica da pergunta {questionIndex}. Por favor, remova-o da lógica primeiro.", diff --git a/packages/lib/utils/videoUpload.ts b/packages/lib/utils/videoUpload.ts index 58e851a019..36563ca74c 100644 --- a/packages/lib/utils/videoUpload.ts +++ b/packages/lib/utils/videoUpload.ts @@ -73,7 +73,7 @@ export const extractYoutubeId = (url: string): string | null => { return false; }); - return id; + return id || null; }; const extractVimeoId = (url: string): string | null => { @@ -96,44 +96,32 @@ const extractLoomId = (url: string): string | null => { return null; }; -// Function to convert watch urls into embed urls and vice versa -export const parseVideoUrl = (url: string): string | undefined => { - // YouTube URL handling +// Always convert a given URL into its embed form if supported. +export const convertToEmbedUrl = (url: string): string | undefined => { + // YouTube if (checkForYoutubeUrl(url)) { - if (url.includes("/embed/")) { - // Reverse parse for YouTube embed URLs - const videoId = url.split("/embed/")[1].split("?")[0]; - return `https://www.youtube.com/watch?v=${videoId}`; - } else { - // Normal parse for YouTube URLs - const videoId = extractYoutubeId(url); - if (videoId) { - return `https://www.youtube.com/embed/${videoId}`; - } + const videoId = extractYoutubeId(url); + if (videoId) { + return `https://www.youtube.com/embed/${videoId}`; } } - // Vimeo URL handling - else if (checkForVimeoUrl(url)) { - if (url.includes("/video/")) { - // Reverse parse for Vimeo embed URLs - const videoId = url.split("/video/")[1].split("?")[0]; - return `https://www.vimeo.com/${videoId}`; - } else { - // Normal parse for Vimeo URLs - const videoId = extractVimeoId(url); + + // Vimeo + if (checkForVimeoUrl(url)) { + const videoId = extractVimeoId(url); + if (videoId) { return `https://player.vimeo.com/video/${videoId}`; } } - // Loom URL handling - else if (checkForLoomUrl(url)) { - if (url.includes("/embed/")) { - // Reverse parse for Loom embed URLs - const videoId = url.split("/embed/")[1].split("?")[0]; - return `https://www.loom.com/share/${videoId}`; - } else { - // Normal parse for Loom URLs - const videoId = extractLoomId(url); + + // Loom + if (checkForLoomUrl(url)) { + const videoId = extractLoomId(url); + if (videoId) { return `https://www.loom.com/embed/${videoId}`; } } + + // If no supported platform found, return undefined + return undefined; }; diff --git a/packages/surveys/src/components/general/question-media.tsx b/packages/surveys/src/components/general/question-media.tsx index 9fe0442be8..5ec21a9862 100644 --- a/packages/surveys/src/components/general/question-media.tsx +++ b/packages/surveys/src/components/general/question-media.tsx @@ -3,7 +3,7 @@ import { checkForLoomUrl, checkForVimeoUrl, checkForYoutubeUrl, - parseVideoUrl, + convertToEmbedUrl, } from "@formbricks/lib/utils/videoUpload"; //Function to add extra params to videoUrls in order to reduce video controls @@ -64,7 +64,7 @@ export function QuestionMedia({ imgUrl, videoUrl, altText = "Image" }: QuestionM ) : null}