diff --git a/client/.env.example b/client/.env.example deleted file mode 100644 index 02b3a30..0000000 --- a/client/.env.example +++ /dev/null @@ -1,11 +0,0 @@ -VITE_FIREBASE_APIKEY=VITE_FIREBASE_APIKEY -VITE_FIREBASE_AUTHDOMAIN=VITE_FIREBASE_AUTHDOMAIN -VITE_FIREBASE_PROJECTID=VITE_FIREBASE_PROJECTID -VITE_FIREBASE_STORAGEBUCKET=VITE_FIREBASE_STORAGEBUCKET -VITE_FIREBASE_MESSAGINGSENDERID=VITE_FIREBASE_MESSAGINGSENDERID -VITE_FIREBASE_APPID=VITE_FIREBASE_APPID -VITE_FIREBASE_MEASUREMENTID=VITE_FIREBASE_MEASUREMENTID - -VITE_FRONTEND_HOSTNAME=localhost:3000 -VITE_BACKEND_HOSTNAME=http://localhost:3001 -VITE_ADMIN_EMAIL="something@gmail.com" \ No newline at end of file diff --git a/client/src/components/forms/createArticle.jsx b/client/src/components/forms/createArticle.jsx new file mode 100644 index 0000000..2b2c559 --- /dev/null +++ b/client/src/components/forms/createArticle.jsx @@ -0,0 +1,202 @@ +import { + Box, + Button, + FormControl, + FormLabel, + FormErrorMessage, + Input, + Stack, + VStack, + } from '@chakra-ui/react'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from "react-hook-form"; +import { useRef, useState } from "react"; +import { z } from "zod"; + +import { useBackendContext } from "../../contexts/hooks/useBackendContext"; + +const createArticleSchema = z.object({ + s3_url: z.string(), + media_url: z.string().url("Please enter a valid URL."), + description: z.string().min(1, "Please write a description of the article."), + title: z.string().min(1, "Your article must include a title.") +}); + +const CreateArticle = () => { + const { backend } = useBackendContext(); + const [fileName, setFileName] = useState(""); + + const [activeButton, setActiveButton] = useState(null); + const toggleButton = (tag) => { + setActiveButton(activeButton === tag ? null : tag); + } + + const fileInputRef = useRef(null); + + const handleFileChange = async (event) => { + if (event.target.files && event.target.files.length > 0) { + const selectedFile = event.target.files[0]; + setFileName(selectedFile.name); + + const formData = new FormData(); + formData.append("file", selectedFile); + } + + const testURL = "https://wawawa.com/ok.jpg"; + setValue("s3_url", testURL, { shouldValidate: true }); + } + + const { + register, + handleSubmit, + setValue, + formState: { errors }, + } = useForm({ + resolver: zodResolver(createArticleSchema), + mode: "onBlur", + }); + + const postArticle = async (data) => { + console.log("Submitting article with data:", data); + + try { + const response = await backend.post("/articles", { + s3_url: data.s3_url ?? "", + media_url: data.media_url ?? "", + description: data.title ?? "", + }); + + if (!response) { + throw new Error("Failed to submit article."); + } + + console.log("Successfully submitted article.") + alert("Article submitted successfully!") + } catch (error) { + console.log("Error submitting article:", error); + alert("Failed to submit article."); + } + }; + + const [selectTags, setSelectTags] = useState([]); + + const tags = ["Ballet", "Classical", "Custom"]; + + const handleTags = (tag) => { + let newTags; + if (selectTags.includes(tag)) { + newTags = selectTags.filter((t) => t !== tag); + } else { + newTags = [...selectTags, tag] + } + + setSelectTags(newTags); + setValue("tag", newTags, {shouldValidate: true}); + } + + return ( + + +
{ + e.preventDefault(); + console.log("form submitted"); + handleSubmit(postArticle)(); + }}> + + Select media to upload. + + + {fileName && ( + + {fileName} + + )} + + {errors.s3_url?.message?.toString()} + + + + + {errors.media_url?.message?.toString()} + + + Select tags for media. + + + + + + {errors.tag?.message} + + + Add a title. + + {errors.title?.message?.toString()} + + +
+
+
+ ) +} + +export default CreateArticle; \ No newline at end of file diff --git a/client/src/components/forms/createVideo.jsx b/client/src/components/forms/createVideo.jsx new file mode 100644 index 0000000..b69c490 --- /dev/null +++ b/client/src/components/forms/createVideo.jsx @@ -0,0 +1,295 @@ +import { useEffect, useRef, useState } from "react"; + +import { + Box, + Button, + FormControl, + FormErrorMessage, + FormLabel, + Image, + Input, + Select, + Stack, + Text, + Textarea, + VisuallyHidden, +} from "@chakra-ui/react"; + +import { useBackendContext } from "../../contexts/hooks/useBackendContext"; +import { VideoCard } from "../resources/VideoCard"; + +function CreateVideo() { + const fileInputRef = useRef(null); + + const [videoData, setVideoData] = useState({ + title: "", + s3_url: "", + description: "", + media_url: "", + class_id: "", + }); + + const [errors, setErrors] = useState({ + title: false, + description: false, + media_url: false, + class_id: false, + s3_url: false, + }); + + const { backend } = useBackendContext(); + + const [activeButton, setActiveButton] = useState(null); + const [selectedFile, setSelectedFile] = useState(null); + const [previewUrl, setPreviewUrl] = useState(null); + const [classes, setClasses] = useState([]); + + const validateForm = () => { + const newErrors = { + title: videoData.title.trim() === "", + description: videoData.description.trim() === "", + media_url: videoData.media_url.trim() === "", + class_id: videoData.class_id.trim() === "", + s3_url: videoData.s3_url.trim() === "", + }; + setErrors(newErrors); + + // Returns true if there are no errors + return ( + !newErrors.title && + !newErrors.description && + !newErrors.media_url && + !newErrors.class_id && + !newErrors.s3_url + ); + }; + + const fetchClasses = async () => { + try { + const classesResponse = await backend.get("/classes"); + setClasses(classesResponse.data); + } catch (error) { + console.error("Error fetching classes:", error); + } + }; + + const toggleButton = (buttonName) => { + setActiveButton((currentActive) => + currentActive === buttonName ? null : buttonName + ); + }; + + const handleFileUploadClick = () => { + fileInputRef.current.click(); + }; + + const handleFileChange = (event) => { + const file = event.target.files[0]; + if (file) { + setSelectedFile(file); + setVideoData({ + ...videoData, + s3_url: `/${file?.name}`, + }); + + const fileReader = new FileReader(); + fileReader.onload = () => { + setPreviewUrl(fileReader.result); + }; + fileReader.readAsDataURL(file); + } + }; + + + const fetchS3URL = async () => { + try { + const URLResponse = await backend.get('/s3/url'); + console.log(URLResponse); + return URLResponse.data.url; + } catch (error) { + console.error('Error fetching S3 URL:', error); + } + }; + + const handleSubmit = async () => { + + const s3Url = await fetchS3URL(); + + const uploadResponse = await fetch(s3Url, { + method: "PUT", + body: selectedFile, + headers: { + "Content-Type": selectedFile.type, + }, + }); + + if (!uploadResponse.ok) { + throw new Error("Failed to upload file"); + } + + + setVideoData({...videoData, s3_url: s3Url}); + + console.log("valid", videoData); + if (validateForm()) { + const res = await backend.post("/classes-videos", { + title: videoData.title ?? "", + s3Url: s3Url ?? "", + description: videoData.description ?? "", + mediaUrl: videoData.media_url ?? "", + classId: videoData.class_id ?? "", + }); + window.location.reload(); // hold on i need to set up a proper form lol + } + }; + + useEffect(() => { + fetchClasses(); + return () => { + if (previewUrl) { + URL.revokeObjectURL(previewUrl); + } + }; + }, [previewUrl]); + + return ( + + + + Upload Image + + + + + + {errors.s3_url && ( + Image is Required + )} + + + Upload Video Embed URL +