mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 18:30:32 -06:00
fix: video embed url (#4485)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
This commit is contained in:
@@ -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<HTMLElement, 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<HTMLElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
setVideoUrlTemp("");
|
||||
setUploadedVideoUrl("");
|
||||
onFileUpload([], "video");
|
||||
};
|
||||
|
||||
const handleVideoUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
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 (
|
||||
<form className="flex flex-col space-y-4">
|
||||
<Label>Video URL(Youtube, Vimeo or Loom):</Label>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<Label>Video URL (YouTube, Vimeo, or Loom):</Label>
|
||||
<div className="flex h-10 items-center space-x-2">
|
||||
<Input
|
||||
className="w-full"
|
||||
placeholder="https://www.youtube.com/embed/VIDEO_ID"
|
||||
placeholder="https://www.youtube.com/watch?v=VIDEO_ID"
|
||||
value={uploadedVideoUrl}
|
||||
onChange={(e) => handleVideoUrlChange(e)}
|
||||
onChange={handleVideoUrlChange}
|
||||
/>
|
||||
{uploadedVideoUrl && videoUrl === uploadedVideoUrl ? (
|
||||
<Button variant="secondary" onClick={(e) => handleRemoveVideo(e)}>
|
||||
Remove
|
||||
<Button variant="secondary" onClick={handleRemoveVideo}>
|
||||
{t("common.remove")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={(e) => handleAddVideo(e)} disabled={isAddButtonDisabled()}>
|
||||
Add
|
||||
<Button onClick={handleAddVideo} disabled={isAddButtonDisabled()}>
|
||||
{t("common.add")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -109,23 +112,19 @@ export const VideoSettings = ({
|
||||
{showPlatformWarning && (
|
||||
<div className="flex items-center space-x-2 rounded-md border bg-slate-100 p-2 text-xs text-slate-600">
|
||||
<AlertTriangle className="h-6 w-6" />
|
||||
<p>
|
||||
Please enter a valid Youtube, Vimeo or Loom Url. We currently do not support other video hosting
|
||||
providers.
|
||||
</p>
|
||||
<p>{t("environments.surveys.edit.invalid_video_url_warning")}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isYoutubeLink && (
|
||||
<AdvancedOptionToggle
|
||||
htmlId="closeOnNumberOfResponse"
|
||||
htmlId="youtubePrivacyMode"
|
||||
isChecked={isYoutubePrivacyModeEnabled}
|
||||
onToggle={() => {
|
||||
toggleYoutubePrivacyMode();
|
||||
}}
|
||||
onToggle={toggleYoutubePrivacyMode}
|
||||
title="YouTube Privacy Mode"
|
||||
description="Keeps user tracking to a minimum"
|
||||
childBorder={true}></AdvancedOptionToggle>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
</div>
|
||||
) : null}
|
||||
<a
|
||||
href={imgUrl ? imgUrl : parseVideoUrl(videoUrl ?? "")}
|
||||
href={imgUrl ? imgUrl : convertToEmbedUrl(videoUrl ?? "")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="fb-absolute fb-bottom-2 fb-right-2 fb-flex fb-items-center fb-gap-2 fb-rounded-md fb-bg-slate-800 fb-bg-opacity-40 fb-p-1.5 fb-text-white fb-opacity-0 fb-backdrop-blur-lg fb-transition fb-duration-300 fb-ease-in-out hover:fb-bg-opacity-65 group-hover/image:fb-opacity-100">
|
||||
|
||||
Reference in New Issue
Block a user