diff --git a/src/App.jsx b/src/App.jsx
index 2c9c42e..e7ab785 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,34 +1,43 @@
-import './App.css'
-import React, { Fragment, useEffect, useState } from 'react';
-import VideoGenerator from './components/VideoGenerator'
-import Header from './components/Header';
-import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
-import ProtectedRoute from './components/ProtectedRoute';
-import Admin from './pages/Admin';
-import SignIn from './components/SignIn';
-import Error from './pages/Error';
-import { initializeFirebase } from './utils/storage';
+import "./App.css";
+import React, { Fragment, useEffect, useState } from "react";
+import VideoGenerator from "./components/VideoGenerator";
+import Header from "./components/Header";
+import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
+import ProtectedRoute from "./components/ProtectedRoute";
+import Admin from "./pages/Admin";
+import SignIn from "./components/SignIn";
+import Error from "./pages/Error";
+import { initializeFirebase } from "./utils/storage";
import { ToastContainer } from "react-toastify";
+import Explore from "./pages/Explore";
import "react-toastify/dist/ReactToastify.css";
function App() {
useEffect(() => {
initializeFirebase();
-}, []);
+ }, []);
return (
-
-
-
-
-
- } />
- } />
- } />
- } />
-
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+ }
+ />
+
-
- )
+
+ );
}
-export default App
+export default App;
diff --git a/src/assets/images/video-placeholder.png b/src/assets/images/video-placeholder.png
new file mode 100644
index 0000000..f559935
Binary files /dev/null and b/src/assets/images/video-placeholder.png differ
diff --git a/src/components/Header.jsx b/src/components/Header.jsx
index 94fdf35..b1fd593 100644
--- a/src/components/Header.jsx
+++ b/src/components/Header.jsx
@@ -1,12 +1,56 @@
-import React from 'react';
-import logoSlogan from '../assets/images/logo_slogan.png'
-
-const Header = () => {
- return (
-
- {/*
*/}
-
- );
-}
-
-export default Header;
\ No newline at end of file
+import React from "react";
+import { NavLink } from "react-router-dom";
+import logoSlogan from "../assets/images/logo_slogan.png";
+
+const Header = () => {
+ return (
+
+
+
+ );
+};
+
+export default Header;
diff --git a/src/components/Modal.jsx b/src/components/Modal.jsx
index 2f5db07..69fce3d 100644
--- a/src/components/Modal.jsx
+++ b/src/components/Modal.jsx
@@ -1,26 +1,33 @@
-import React from 'react';
+import React from "react";
const Modal = ({ isOpen, onClose, children, showOkButton = true }) => {
- if (!isOpen) return null;
+ if (!isOpen) return null;
- return (
-
-
-
-
-
-
- {children}
-
- {showOkButton && (
-
-
-
- )}
-
-
+ return (
+
+
+
+
- );
+
{children}
+ {showOkButton && (
+
+
+
+ )}
+
+
+ );
};
-export default Modal;
\ No newline at end of file
+export default Modal;
diff --git a/src/components/VideoGenerator.jsx b/src/components/VideoGenerator.jsx
index 8e090b9..c97b964 100644
--- a/src/components/VideoGenerator.jsx
+++ b/src/components/VideoGenerator.jsx
@@ -1,11 +1,18 @@
import React, { useState, useEffect } from "react";
import axios from "axios";
import loadingMessages from "../utils/loadingMessages.js";
-import { getFileUrl, uploadFile, deleteFile, addDocument, getFirestoreData, getCollectionDocs } from "../utils/storage.js";
+import {
+ getFileUrl,
+ uploadFile,
+ deleteFile,
+ addDocument,
+ getFirestoreData,
+ getCollectionDocs,
+} from "../utils/storage.js";
import uploadGeneratedVideosForFeed from "../utils/uploadGeneratedVideosForFeed.js";
-import logoSlogan from '../assets/images/logo_slogan.png';
+import logoSlogan from "../assets/images/logo_slogan.png";
import VideoDownloader from "./VideoDownloader.jsx";
-import Feed from './Feed';
+import Feed from "./Feed";
import TikTokIcon from "../assets/icons/TikTokIcon.jsx";
import Modal from "./Modal.jsx";
import TermsOfService from "./TermsOfService.jsx";
@@ -13,515 +20,574 @@ import { toast } from "react-toastify";
import Card from "./Card.jsx";
function VideoGenerator() {
- const [ videoFile, setVideoFile ] = useState(null);
- const [ messageIsCritial, setMessageIsCritial ] = useState(false);
- const [ loading, setLoading ] = useState(false);
- const [ uploading, setUploading ] = useState(false);
- const [ status, setStatus ] = useState("");
- const [ downloadUrl, setDownloadUrl ] = useState("");
- const [ previewUrl, setPreviewUrl ] = useState("");
- const [ loadingMessageIndex, setLoadingMessageIndex ] = useState(0);
- const [ progress, setProgress ] = useState(0);
- const [ campaigns, setCampaigns ] = useState([]);
- const [ currentCampaign, setCurrentCampaign ] = useState(0);
- const [ generationData, setGenerationData ] = useState(null);
- const [ isModalOpen, setIsModalOpen ] = useState(false);
- const [isVideoModalOpen, setIsVideoModalOpen] = useState(false);
- const [videoToPreview, setVideoToPreview] = useState("");
- const [ hasAcceptedTerms, setHasAcceptedTerms ] = useState(false);
- const [ showError, setShowError ] = useState(false);
- const [ isProcessingVideo, setIsProcessingVideo ] = useState(false);
- const [ email, setEmail ] = useState("");
- const [ isValidEmail, setIsValidEmail ] = useState(false);
- const [ isAuthenticated, setIsAuthenticated ] = useState(false);
- const isLocal = import.meta.env.VITE_NODE_ENV === "development" || !import.meta.env.VITE_API_BASE_URL;
- const baseUrl = isLocal ? "http://localhost:5000" : import.meta.env.VITE_API_BASE_URL;
- const clipLength = 5;
-
- useEffect(() => {
- if (!loading) return;
- const interval = setInterval(() => {
- setLoadingMessageIndex((prevIndex) => (prevIndex + 1) % generationData.loadingMessages.length);
- }, 2000);
-
- return () => clearInterval(interval);
- }, [loading]);
-
- useEffect(() => {
- fetchCampaigns();
- }, [])
-
- useEffect(() => {
- const storedEmail = localStorage.getItem("email");
- if (storedEmail) {
- setEmail(storedEmail);
- setIsValidEmail(/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(storedEmail));
- }
- }, []);
-
- useEffect(() => {
- if (campaigns.length > 0 && !generationData) {
- setGenerationData({
- audioUrl: campaigns[0].audio,
- prompt: campaigns[0].prompt,
- generationType: campaigns[0].id,
- doubleGeneration: campaigns[0].doubleGeneration,
- loadingMessages: JSON.parse(campaigns[0].loadingMessages)
- });
- }
- }, [campaigns]);
-
- const getVideoProcessProgress = () => {
- const duration = 500;
- const intervalTime = 1000; // 1 second
- const increment = 100 / duration; // Increment per second
-
- setProgress(0); // Reset progress
-
- const interval = setInterval(() => {
- setProgress((prevProgress) => {
- const newProgress = prevProgress + increment;
- if (newProgress >= 100) {
- clearInterval(interval);
- return 100;
- }
- return newProgress;
- });
- }, intervalTime);
- };
-
- const fetchCampaigns = async () => {
- try {
- const campaigns = await getCollectionDocs("campaigns");
- const sortedCampaigns = campaigns.sort((a, b) => a.sort - b.sort);
- setCampaigns(sortedCampaigns);
- } catch (error) {
- console.error("Error fetching campaigns:", error);
- }
+ const [videoFile, setVideoFile] = useState(null);
+ const [messageIsCritial, setMessageIsCritial] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [uploading, setUploading] = useState(false);
+ const [status, setStatus] = useState("");
+ const [downloadUrl, setDownloadUrl] = useState("");
+ const [previewUrl, setPreviewUrl] = useState("");
+ const [loadingMessageIndex, setLoadingMessageIndex] = useState(0);
+ const [progress, setProgress] = useState(0);
+ const [campaigns, setCampaigns] = useState([]);
+ const [currentCampaign, setCurrentCampaign] = useState(0);
+ const [generationData, setGenerationData] = useState(null);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [isVideoModalOpen, setIsVideoModalOpen] = useState(false);
+ const [videoToPreview, setVideoToPreview] = useState("");
+ const [hasAcceptedTerms, setHasAcceptedTerms] = useState(false);
+ const [showError, setShowError] = useState(false);
+ const [isProcessingVideo, setIsProcessingVideo] = useState(false);
+ const [email, setEmail] = useState("");
+ const [isValidEmail, setIsValidEmail] = useState(false);
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const isLocal =
+ import.meta.env.VITE_NODE_ENV === "development" ||
+ !import.meta.env.VITE_API_BASE_URL;
+ const baseUrl = isLocal
+ ? "http://localhost:5000"
+ : import.meta.env.VITE_API_BASE_URL;
+ const clipLength = 5;
+
+ useEffect(() => {
+ if (!loading) return;
+ const interval = setInterval(() => {
+ setLoadingMessageIndex(
+ (prevIndex) => (prevIndex + 1) % generationData.loadingMessages.length
+ );
+ }, 2000);
+
+ return () => clearInterval(interval);
+ }, [loading]);
+
+ useEffect(() => {
+ fetchCampaigns();
+ }, []);
+
+ useEffect(() => {
+ const storedEmail = localStorage.getItem("email");
+ if (storedEmail) {
+ setEmail(storedEmail);
+ setIsValidEmail(/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(storedEmail));
}
+ }, []);
+
+ useEffect(() => {
+ if (campaigns.length > 0 && !generationData) {
+ setGenerationData({
+ audioUrl: campaigns[0].audio,
+ prompt: campaigns[0].prompt,
+ generationType: campaigns[0].id,
+ doubleGeneration: campaigns[0].doubleGeneration,
+ loadingMessages: JSON.parse(campaigns[0].loadingMessages),
+ });
+ }
+ }, [campaigns]);
- const handleVideoUpload = async (event) => {
- setUploading(true);
- const file = event.target.files[0];
-
- if (!file) {
- console.error("No file selected");
- setUploading(false);
- return;
- }
-
- const fileSizeMB = file.size / (1024 * 1024);
- if (fileSizeMB > 80) {
- console.error("File is larger than 80 MB");
- handleCriticalError("*File must be 80 MB or smaller.");
- setUploading(false);
- return;
- }
-
- setVideoFile(file); // Store the selected video file
- setStatus("Video uploaded successfully.");
- setUploading(false);
- };
-
- const processVideo = async () => {
- if (!videoFile) {
- alert("Please select a video file.");
- return;
+ const getVideoProcessProgress = () => {
+ const duration = 500;
+ const intervalTime = 1000; // 1 second
+ const increment = 100 / duration; // Increment per second
+
+ setProgress(0); // Reset progress
+
+ const interval = setInterval(() => {
+ setProgress((prevProgress) => {
+ const newProgress = prevProgress + increment;
+ if (newProgress >= 100) {
+ clearInterval(interval);
+ return 100;
}
+ return newProgress;
+ });
+ }, intervalTime);
+ };
+
+ const fetchCampaigns = async () => {
+ try {
+ const campaigns = await getCollectionDocs("campaigns");
+ const sortedCampaigns = campaigns.sort((a, b) => a.sort - b.sort);
+ setCampaigns(sortedCampaigns);
+ } catch (error) {
+ console.error("Error fetching campaigns:", error);
+ }
+ };
+
+ const handleVideoUpload = async (event) => {
+ setUploading(true);
+ const file = event.target.files[0];
- const currentEmail = (email && isValidEmail && email != "") ? email : null;
-
- setIsProcessingVideo(true);
- setProgress(0);
- getVideoProcessProgress();
- setLoading(true);
- setDownloadUrl("");
-
- const formData = new FormData();
- formData.append("originalVideo", videoFile);
- formData.append("prompt", generationData.prompt);
- formData.append("clipLength", clipLength);
- formData.append("audioUrl", generationData.audioUrl);
- formData.append("generationType", generationData.generationType);
- formData.append("email", email);
-
- try {
- // Step 1: Get Task Id
- const response = await axios.post(`${baseUrl}/api/get-task-id`, formData, {
- headers: { "Content-Type": "multipart/form-data" },
- });
-
- let { task_id, trimmed_video } = response.data;
-
- if (task_id) {
- localStorage.setItem("task_id", task_id);
- }
-
- //TODO: swap before merge
- // task_id = '241528415490229';
- await waitBeforePolling();
-
- const file_id = await pollMiniMaxForVideo(task_id);
- console.log("🎥 AI-generated video File ID:", file_id);
-
- formData.append("aiVideoFileId", file_id);
- formData.append("trimmedVideo", trimmed_video);
-
- // Step 3: Wait for the final processed video
- const finalVideoResponse = await axios.post(
- `${baseUrl}/api/complete-video`,
- JSON.stringify({
- aiVideoFileId: file_id,
- trimmedVideo: trimmed_video,
- audioUrl: generationData.audioUrl,
- doubleGeneration: generationData.doubleGeneration,
- clipLength,
- generationType: generationData.generationType,
- email: currentEmail
- }),
- {
- headers: { "Content-Type": "application/json" },
- responseType: "json"
- }
- );
-
- setDownloadUrl(finalVideoResponse.data.videoUrl);
- console.log("✅ Final video ready:", finalVideoResponse.data.videoUrl);
-
- setPreviewUrl(finalVideoResponse.data.videoUrl);
-
- } catch (error) {
- console.error("❌ Error processing video:", error);
- setIsProcessingVideo(false);
- window.location.href = "/error";
- } finally {
- setLoading(false);
- setIsProcessingVideo(false);
+ if (!file) {
+ console.error("No file selected");
+ setUploading(false);
+ return;
+ }
+
+ const fileSizeMB = file.size / (1024 * 1024);
+ if (fileSizeMB > 80) {
+ console.error("File is larger than 80 MB");
+ handleCriticalError("*File must be 80 MB or smaller.");
+ setUploading(false);
+ return;
+ }
+
+ setVideoFile(file); // Store the selected video file
+ setStatus("Video uploaded successfully.");
+ setUploading(false);
+ };
+
+ const processVideo = async () => {
+ if (!videoFile) {
+ alert("Please select a video file.");
+ return;
+ }
+
+ const currentEmail = email && isValidEmail && email != "" ? email : null;
+
+ setIsProcessingVideo(true);
+ setProgress(0);
+ getVideoProcessProgress();
+ setLoading(true);
+ setDownloadUrl("");
+
+ const formData = new FormData();
+ formData.append("originalVideo", videoFile);
+ formData.append("prompt", generationData.prompt);
+ formData.append("clipLength", clipLength);
+ formData.append("audioUrl", generationData.audioUrl);
+ formData.append("generationType", generationData.generationType);
+ formData.append("email", email);
+
+ try {
+ // Step 1: Get Task Id
+ const response = await axios.post(
+ `${baseUrl}/api/get-task-id`,
+ formData,
+ {
+ headers: { "Content-Type": "multipart/form-data" },
}
- };
-
- const waitBeforePolling = () => {
- return new Promise(resolve => {
- console.log("⏳ Waiting for AI Generation...");
- let messages = [
- "⏳ Waiting: Holding for AI magic...",
- "✨ Waiting: Creating AI-powered video...",
- "🔄 Waiting: Processing, hang tight...",
- "🚀 Waiting: AI is working hard on this...",
- "🎬 Waiting: Finalizing the masterpiece...",
- "🤖 Waiting: Bringing AI visuals to life...",
- "📽️ Waiting: Almost there, just a little longer..."
- ];
-
- let attempt = 0;
- const interval = setInterval(() => {
- console.log(messages[attempt % messages.length]); // Cycle through messages
- attempt++;
- }, 30000);
-
- setTimeout(() => {
- clearInterval(interval);
- console.log("✅ Starting polling...");
- resolve();
- }, 180000); // 180000ms = 3 minutes
- });
- };
-
- const pollMiniMaxForVideo = async (taskId) => {
- const pollInterval = 15000; // 15 seconds
- const maxRetries = 10; // 🔹 Max attempts before erroring out
- let attempts = 0;
-
- console.log(`⏳ Starting polling for AI video with Task ID: ${taskId}`);
-
- while (attempts < maxRetries) {
- try {
- const response = await fetch("/.netlify/functions/poll-ai-video", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ taskId }),
- });
-
- const data = await response.json();
-
- if (data.status === "Success") {
- console.log("✅ AI Video Ready:", data.file_id);
- return data.file_id; // Return file_id when done
- }
-
- console.log(`⏳ Video still processing... Attempt ${attempts + 1} of ${maxRetries}`);
- } catch (error) {
- console.error("❌ Error polling MiniMax:", error);
- }
-
- attempts++;
- if (attempts >= maxRetries) {
- console.error("❌ Maximum polling attempts reached. AI Video not ready.");
- throw new Error("AI video processing timed out after 10 attempts.");
- }
-
- await new Promise(res => setTimeout(res, pollInterval));
+ );
+
+ let { task_id, trimmed_video } = response.data;
+
+ if (task_id) {
+ localStorage.setItem("task_id", task_id);
+ }
+
+ //TODO: swap before merge
+ // task_id = '241528415490229';
+ await waitBeforePolling();
+
+ const file_id = await pollMiniMaxForVideo(task_id);
+ console.log("🎥 AI-generated video File ID:", file_id);
+
+ formData.append("aiVideoFileId", file_id);
+ formData.append("trimmedVideo", trimmed_video);
+
+ // Step 3: Wait for the final processed video
+ const finalVideoResponse = await axios.post(
+ `${baseUrl}/api/complete-video`,
+ JSON.stringify({
+ aiVideoFileId: file_id,
+ trimmedVideo: trimmed_video,
+ audioUrl: generationData.audioUrl,
+ doubleGeneration: generationData.doubleGeneration,
+ clipLength,
+ generationType: generationData.generationType,
+ email: currentEmail,
+ }),
+ {
+ headers: { "Content-Type": "application/json" },
+ responseType: "json",
}
- };
-
- const handleEnterEmail = (event) => {
- const emailValue = event.target.value;
- setEmail(emailValue);
-
- // Regular expression for basic email validation
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-
- if (emailValue === "" || emailRegex.test(emailValue)) {
- setIsValidEmail(true);
- localStorage.setItem("email", emailValue);
- localStorage.setItem("emailValid", true);
- } else {
- setIsValidEmail(false);
- console.error("Invalid email address");
+ );
+
+ setDownloadUrl(finalVideoResponse.data.videoUrl);
+ console.log("✅ Final video ready:", finalVideoResponse.data.videoUrl);
+
+ setPreviewUrl(finalVideoResponse.data.videoUrl);
+ } catch (error) {
+ console.error("❌ Error processing video:", error);
+ setIsProcessingVideo(false);
+ window.location.href = "/error";
+ } finally {
+ setLoading(false);
+ setIsProcessingVideo(false);
+ }
+ };
+
+ const waitBeforePolling = () => {
+ return new Promise((resolve) => {
+ console.log("⏳ Waiting for AI Generation...");
+ let messages = [
+ "⏳ Waiting: Holding for AI magic...",
+ "✨ Waiting: Creating AI-powered video...",
+ "🔄 Waiting: Processing, hang tight...",
+ "🚀 Waiting: AI is working hard on this...",
+ "🎬 Waiting: Finalizing the masterpiece...",
+ "🤖 Waiting: Bringing AI visuals to life...",
+ "📽️ Waiting: Almost there, just a little longer...",
+ ];
+
+ let attempt = 0;
+ const interval = setInterval(() => {
+ console.log(messages[attempt % messages.length]); // Cycle through messages
+ attempt++;
+ }, 30000);
+
+ setTimeout(() => {
+ clearInterval(interval);
+ console.log("✅ Starting polling...");
+ resolve();
+ }, 180000); // 180000ms = 3 minutes
+ });
+ };
+
+ const pollMiniMaxForVideo = async (taskId) => {
+ const pollInterval = 15000; // 15 seconds
+ const maxRetries = 10; // 🔹 Max attempts before erroring out
+ let attempts = 0;
+
+ console.log(`⏳ Starting polling for AI video with Task ID: ${taskId}`);
+
+ while (attempts < maxRetries) {
+ try {
+ const response = await fetch("/.netlify/functions/poll-ai-video", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ taskId }),
+ });
+
+ const data = await response.json();
+
+ if (data.status === "Success") {
+ console.log("✅ AI Video Ready:", data.file_id);
+ return data.file_id; // Return file_id when done
}
- };
- const handleUpdateStatus = (message) =>{
- setMessageIsCritial(false);
- setStatus(message);
+ console.log(
+ `⏳ Video still processing... Attempt ${
+ attempts + 1
+ } of ${maxRetries}`
+ );
+ } catch (error) {
+ console.error("❌ Error polling MiniMax:", error);
+ }
+
+ attempts++;
+ if (attempts >= maxRetries) {
+ console.error(
+ "❌ Maximum polling attempts reached. AI Video not ready."
+ );
+ throw new Error("AI video processing timed out after 10 attempts.");
+ }
+
+ await new Promise((res) => setTimeout(res, pollInterval));
+ }
+ };
+
+ const handleEnterEmail = (event) => {
+ const emailValue = event.target.value;
+ setEmail(emailValue);
+
+ // Regular expression for basic email validation
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+
+ if (emailValue === "" || emailRegex.test(emailValue)) {
+ setIsValidEmail(true);
+ localStorage.setItem("email", emailValue);
+ localStorage.setItem("emailValid", true);
+ } else {
+ setIsValidEmail(false);
+ console.error("Invalid email address");
+ }
+ };
+
+ const handleUpdateStatus = (message) => {
+ setMessageIsCritial(false);
+ setStatus(message);
+ };
+
+ const handleCriticalError = (message) => {
+ setMessageIsCritial(true);
+ setStatus(message);
+ setLoading(false);
+ };
+
+ const handleCheckboxChange = (event) => {
+ setHasAcceptedTerms(event.target.checked);
+ };
+
+ const handleSelectContent = (campaignIdx) => {
+ setCurrentCampaign(campaignIdx);
+ setGenerationData({
+ audioUrl: campaigns[campaignIdx].audio,
+ prompt: campaigns[campaignIdx].prompt,
+ generationType: campaigns[campaignIdx].id,
+ doubleGeneration: campaigns[campaignIdx].doubleGeneration,
+ loadingMessages: JSON.parse(campaigns[campaignIdx].loadingMessages),
+ });
+ };
+
+ const handleOpenVideoModal = (videoUrl) => {
+ setVideoToPreview(videoUrl);
+ setIsVideoModalOpen(true);
+ };
+
+ const handleGenerateVideo = async () => {
+ if (!videoFile) {
+ handleUpdateStatus("Please upload a video file.");
+ toast.error("Please upload a video to start your generation.");
+ return;
}
- const handleCriticalError = (message) => {
- setMessageIsCritial(true);
- setStatus(message);
- setLoading(false);
+ if (!hasAcceptedTerms) {
+ setShowError(true);
+ setTimeout(() => {
+ setShowError(false);
+ }, 5000);
+ return;
}
- const handleCheckboxChange = (event) => {
- setHasAcceptedTerms(event.target.checked);
- };
-
- const handleSelectContent = (campaignIdx) => {
- setCurrentCampaign(campaignIdx);
- setGenerationData({
- audioUrl: campaigns[campaignIdx].audio,
- prompt: campaigns[campaignIdx].prompt,
- generationType: campaigns[campaignIdx].id,
- doubleGeneration: campaigns[campaignIdx].doubleGeneration,
- loadingMessages: JSON.parse(campaigns[campaignIdx].loadingMessages)
- });
- };
-
- const handleOpenVideoModal = (videoUrl) => {
- setVideoToPreview(videoUrl);
- setIsVideoModalOpen(true);
- };
-
- const handleGenerateVideo = async () => {
- if (!videoFile) {
- handleUpdateStatus("Please upload a video file.");
- toast.error("Please upload a video to start your generation.");
- return;
- }
+ // if (!isValidEmail && email != "") {
+ // handleUpdateStatus("Please enter a valid email.");
+ // toast.error("Please enter a valid email.");
+ // return;
+ // }
- if (!hasAcceptedTerms) {
- setShowError(true);
- setTimeout(() => {
- setShowError(false);
- }, 5000);
- return;
- }
+ processVideo();
+ };
- // if (!isValidEmail && email != "") {
- // handleUpdateStatus("Please enter a valid email.");
- // toast.error("Please enter a valid email.");
- // return;
- // }
-
- processVideo();
- };
-
- const handleShareToTikTok = async () => {
- if (!downloadUrl) {
- console.error("No video available to share.");
- return;
- }
-
- const link = document.createElement("a");
- link.href = downloadUrl;
- link.setAttribute("download", "miceband_video.mp4");
- document.body.appendChild(link);
-
- link.click();
-
- document.body.removeChild(link);
-
- setTimeout(() => {
- window.location.href = "https://www.tiktok.com/login?lang=en&redirect_url=https%3A%2F%2Fwww.tiktok.com%2Fupload";
- }, 2000);
- };
-
- return (
-
-
-
-

- {/* ***** Email ***** */}
- {/*
+ const handleShareToTikTok = async () => {
+ if (!downloadUrl) {
+ console.error("No video available to share.");
+ return;
+ }
+
+ const link = document.createElement("a");
+ link.href = downloadUrl;
+ link.setAttribute("download", "miceband_video.mp4");
+ document.body.appendChild(link);
+
+ link.click();
+
+ document.body.removeChild(link);
+
+ setTimeout(() => {
+ window.location.href =
+ "https://www.tiktok.com/login?lang=en&redirect_url=https%3A%2F%2Fwww.tiktok.com%2Fupload";
+ }, 2000);
+ };
+
+ return (
+
+
+
+

+ {/* ***** Email ***** */}
+ {/*
Email (Optional)
We'll send you a link to download your video.
{!isValidEmail && email != "" &&
*Please enter a valid email.
}
*/}
-
-
- {/* ***** Upload ***** */}
-
-
Upload a video
-
-
-
- {showError &&
Please accept the terms of service.}
-
- {/* ***** Generate ***** */}
-
-
Generate video
- {(uploading || loading) ? (
-
- ) : (
-
- )}
-
-
-
setIsModalOpen(false)}
- >
-
-
-
-
-
-
-
-
-
-
-
-
- {loading && (
-
-
-
-
-
-
-
-
- {isProcessingVideo && (
-
- )}
-
{generationData.loadingMessages[loadingMessageIndex]}
-
- )}
-
- {loading && (
Your video is being processed. This could take up to 5 minutes. **Please don't close the page.
)}
- {status &&
{status}
}
-
- {downloadUrl && previewUrl && (
-
- )}
-
- {/* ***** Select Content ***** */}
-
-
Select your content
-
- {campaigns.length > 0 && generationData ? (
- campaigns
- .filter(campaign => campaign) // Ensure campaign is not undefined/null
- .map((campaign, idx) => (
-
handleSelectContent(idx)}
- imageUrl={campaign?.image || ""}
- name={campaign?.name || "Unknown"}
- isSelected={currentCampaign === idx}
- loading={loading}
- />
- ))
- ) : (
- Loading Campaigns...
- )}
-
-
-
- {/* ***** Video Modal ***** */}
-
setIsVideoModalOpen(false)}
- >
- Preview Video
-
-
-
-
-
-
-
+
+ {/* ***** Upload ***** */}
+
+
+ Upload a video
+
+
+
+
+ {showError && (
+
+ Please accept the terms of service.
+
+ )}
+
+ {/* ***** Generate ***** */}
+
+
+ Generate video
+
+ {uploading || loading ? (
+
+ ) : (
+
+ )}
+
+
+
setIsModalOpen(false)}>
+
+
+
+
+
+
+
+
+ {loading && (
+
+
+
+
+
+
+
+
+ {isProcessingVideo && (
+
+ )}
+
{generationData.loadingMessages[loadingMessageIndex]}
+
+ )}
+
+ {loading && (
+
+ Your video is being processed. This could take up to 5 minutes.{" "}
+
+ **Please don't close the page.
+
+
+ )}
+ {status && (
+
+ {status}
+
+ )}
+
+ {downloadUrl && previewUrl && (
+
+ )}
+
+ {/* ***** Select Content ***** */}
+
+
+ Select your content
+
+
+ {campaigns.length > 0 && generationData ? (
+ campaigns
+ .filter((campaign) => campaign) // Ensure campaign is not undefined/null
+ .map((campaign, idx) => (
+
handleSelectContent(idx)}
+ imageUrl={campaign?.image || ""}
+ name={campaign?.name || "Unknown"}
+ isSelected={currentCampaign === idx}
+ loading={loading}
+ />
+ ))
+ ) : (
+ Loading Campaigns...
+ )}
+
+
+ {/* ***** Video Modal ***** */}
+
setIsVideoModalOpen(false)}
+ >
+ Preview Video
+
+
+
+
+
+
+
- );
+
+
+ );
}
export default VideoGenerator;
diff --git a/src/pages/Explore.jsx b/src/pages/Explore.jsx
new file mode 100644
index 0000000..affb3d1
--- /dev/null
+++ b/src/pages/Explore.jsx
@@ -0,0 +1,142 @@
+import { useState, useEffect, useRef, useCallback } from "react";
+import { useNavigate } from "react-router-dom";
+import { getCollectionDocs } from "../utils/storage.js";
+import Modal from "../components/Modal.jsx";
+import Loader from "../components/Loader.jsx";
+import { useInView } from "react-intersection-observer";
+import videoPlaceholder from "../assets/images/video-placeholder.png";
+
+const VideoCard = ({ videoUrl, onClick }) => {
+ const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.1 });
+
+ const handleMouseEnter = (e) => e.target.play();
+
+ const handleMouseLeave = (e) => {
+ e.target.pause();
+ e.target.currentTime = 0; // Reset to the start
+ };
+
+ return (
+
+ {inView ? (
+
+ ) : (
+

+ )}
+
+ );
+};
+
+const Explore = () => {
+ const [generatedVideos, setGeneratedVideos] = useState([]);
+ const [selectedVideo, setSelectedVideo] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState("");
+ const navigate = useNavigate();
+
+ // Shuffle Function
+ const shuffleArray = (array) => array.sort(() => Math.random() - 0.5);
+
+ useEffect(() => {
+ const fetchVideos = async () => {
+ setIsLoading(true);
+ setError("");
+
+ try {
+ const videos = await getCollectionDocs("videos");
+ if (videos.length > 0) {
+ const shuffledVideos = shuffleArray(videos).slice(0, 30);
+ setGeneratedVideos(shuffledVideos);
+ } else {
+ setError("No videos found. Please try again later.");
+ }
+ } catch (error) {
+ console.error("Error fetching videos:", error);
+ setError(
+ "Failed to load videos. Please check your connection and try again."
+ );
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchVideos();
+ }, []);
+
+ // Modal Handling
+ const handleVideoClick = (videoUrl) => setSelectedVideo(videoUrl);
+ const closeModal = () => setSelectedVideo(null);
+
+ return (
+
+
+
+
+
+ The BEST last-frame AI meme content generator.
+
+
+ Automatically mix, match, and morph any video with your own AI
+ creation.
+
+
+
+
+
+ {isLoading ? (
+
+
+
+ ) : error ? (
+
{error}
+ ) : (
+
+ {generatedVideos.map((video) => (
+ handleVideoClick(video.url)}
+ />
+ ))}
+
+ )}
+
+
+ {/* Video Modal */}
+
+ {selectedVideo && (
+
+
+
+ )}
+
+
+ );
+};
+
+export default Explore;