아래와 같이 비디오를 업로드 할 수 있는 페이지를 구현할 것이다.
✅ 상태 관리
비디오 업로드를 하려면 비디오 제목과 내용, 프라이빗 여부, 카테고리, 파일 경로, 시간, 썸네일 경로 상태를 생성해준다.
const user = useSelector((state) => state.user); // redux state에서 user 가져오기
const [VideoTitle, setVideoTitle] = useState("");
const [Description, setDescription] = useState("");
const [Private, setPrivate] = useState(0);
const [Category, setCategory] = useState("Film & Animation");
const [FilePath, setFilePath] = useState("");
const [Duration, setDuration] = useState("");
const [ThumbnailPath, setThumbnailPath] = useState("");
✅ select 배열 정리
프라이빗 여부와 카테고리 같은 경우는 select와 option 형태이기 때문에 배열을 활용해 map 메서드를 사용해주는 것이 더 깔끔하다. 그렇기 때문에 아래와 같이 배열을 생성해준다.
const PrivateOptions = [
{ value: 0, label: "Private" },
{ value: 1, label: "Public" },
];
const CategoryOptions = [
{ value: 0, label: "Film & Animation" },
{ value: 1, label: "Autos & Vehicles" },
{ value: 2, label: "Music" },
{ value: 3, label: "Pets & Animals" },
];
✅ 비디오 업로드 Dropzone
일단, 비디오를 Drop하여 업로드할 수 있도록 Dropzone을 다운 받는다.
npm i dropzone
일반적으로 클라이언트에서 서버로 데이터가 전송될 때 인코딩되어 전송된다. 이러한 방식은 JSON 형식의 데이터를 전송하는 것에는 어려움이 없으나, 파일 자체를 전송하려 할 때는 생각한 것처럼 전송되지 않는다. 그래서 파일을 입력하는 form 태그의 encType 속성을 먼저, multipart/form-data로 바꿔줘야 한다.
업로드 성공을 했으면 이제 썸네일을 나오게 할 건데, 썸네일에게 주어져야 할 정보는 비디오 파일 경로와 비디오 파일명이다. 성공적으로 서버에게 받아왔으면 상태를 업데이트 해준다.
const handleDrop = (files) => {
let formData = new FormData();
const config = {
header: { "content-type": "multipart/form-data" },
};
formData.append("file", files[0]);
axios.post("/api/video/uploadfiles", formData, config).then((res) => {
if (res.data.success) {
let variable = {
filePath: res.data.filePath,
fileName: res.data.fileName,
};
setFilePath(res.data.filePath);
axios.post("/api/video/thumbnail", variable).then((res) => {
if (res.data.success) {
setThumbnailPath(res.data.thumbsFilePath);
setDuration(res.data.fileDuration);
} else {
alert("Failed to make the thumbnails");
}
});
} else {
alert("failed to save the video in server");
}
});
};
✅ form 제출
마지막으로, 정보를 다 넣으면 폼을 제출해줘야한다.
useSelector를 활용하여 redux state에서 user를 가져왔고, 이를 활용해 글쓴이, 비디오 제목과 내용, 프라이빗 여부, 카테고리, 파일 경로, 시간, 썸네일 경로 상태를 body에 담아 서버로 보내줘 DB에 담길 수 있도록 하고, 성공한다면 3초 뒤에 메인 화면으로 가도록 구현했다.
const handleSubmit = (e) => {
e.preventDefault();
const variables = {
writer: user.userData._id,
title: VideoTitle,
description: Description,
privacy: Private,
filePath: FilePath,
category: Category,
duration: Duration,
thumbnail: ThumbnailPath,
};
axios.post("/api/video/uploadVideo", variables).then((res) => {
if (res.data.success) {
alert("비디오 업로드에 성공했습니다.");
setTimeout(() => {
props.history.push("/");
}, 3000);
} else {
alert("비디오 업로드에 실패했습니다.");
}
});
};
✅ 최종코드
import React, { useState } from "react";
import { Typography, Button, Form, Input, Icon } from "antd";
import Dropzone from "react-dropzone";
import axios from "axios";
import { useSelector } from "react-redux";
const { Title } = Typography;
const { TextArea } = Input;
const PrivateOptions = [
{ value: 0, label: "Private" },
{ value: 1, label: "Public" },
];
const CategoryOptions = [
{ value: 0, label: "Film & Animation" },
{ value: 1, label: "Autos & Vehicles" },
{ value: 2, label: "Music" },
{ value: 3, label: "Pets & Animals" },
];
function VideoUploadPage(props) {
const user = useSelector((state) => state.user); // redux state에서 user 가져오기
const [VideoTitle, setVideoTitle] = useState("");
const [Description, setDescription] = useState("");
const [Private, setPrivate] = useState(0);
const [Category, setCategory] = useState("Film & Animation");
const [FilePath, setFilePath] = useState("");
const [Duration, setDuration] = useState("");
const [ThumbnailPath, setThumbnailPath] = useState("");
const handleChange = (e) => {
const { value, name } = e.target;
if (name === "title") {
setVideoTitle(value);
} else if (name === "description") {
setDescription(value);
} else if (name === "private") {
setPrivate(value);
} else if (name === "category") {
setCategory(value);
}
};
const handleDrop = (files) => {
let formData = new FormData();
const config = {
header: { "content-type": "multipart/form-data" },
};
console.log(files);
formData.append("file", files[0]);
axios.post("/api/video/uploadfiles", formData, config).then((res) => {
if (res.data.success) {
let variable = {
filePath: res.data.filePath,
fileName: res.data.fileName,
};
setFilePath(res.data.filePath);
axios.post("/api/video/thumbnail", variable).then((res) => {
if (res.data.success) {
console.log(res.data);
setThumbnailPath(res.data.thumbsFilePath);
setDuration(res.data.fileDuration);
} else {
alert("Failed to make the thumbnails");
}
});
} else {
alert("failed to save the video in server");
}
});
};
const handleSubmit = (e) => {
e.preventDefault();
const variables = {
writer: user.userData._id,
title: VideoTitle,
description: Description,
privacy: Private,
filePath: FilePath,
category: Category,
duration: Duration,
thumbnail: ThumbnailPath,
};
axios.post("/api/video/uploadVideo", variables).then((res) => {
if (res.data.success) {
alert("비디오 업로드에 성공했습니다.");
setTimeout(() => {
props.history.push("/");
}, 3000);
} else {
alert("비디오 업로드에 실패했습니다.");
}
});
};
return (
<div style={{ maxWidth: "700px", margin: "2rem auto" }}>
<div style={{ textAlign: "center", marginBottom: "2rem" }}>
<Title level={2}> Upload Video</Title>
</div>
<Form onSubmit={handleSubmit}>
<div style={{ display: "flex", justifyContent: "space-between" }}>
{/* DropZone */}
<Dropzone onDrop={handleDrop} multiple={false} maxSize={800000000}>
{({ getRootProps, getInputProps }) => (
<div
style={{
width: "300px",
height: "240px",
border: "1px solid lightgray",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
{...getRootProps()}
>
<input {...getInputProps()} />
<Icon type="plus" style={{ fontSize: "3rem" }} />
</div>
)}
</Dropzone>
{/* Thumbnail */}
{ThumbnailPath && (
<div>
<img
src={`http://localhost:5000/${ThumbnailPath}`}
alt="thumbnail"
/>
</div>
)}
</div>
<br />
<br />
<label>Title</label>
<Input onChange={handleChange} name="title" value={VideoTitle} />
<br />
<br />
<label>Description</label>
<TextArea
onChange={handleChange}
name="description"
value={Description}
/>
<br />
<br />
<select onChange={handleChange} name="private">
{PrivateOptions.map((item, index) => (
<option key={index} value={item.value}>
{item.label}
</option>
))}
</select>
<br />
<br />
<select onChange={handleChange} name="category">
{CategoryOptions.map((item, index) => (
<option key={index} value={item.value}>
{item.label}
</option>
))}
</select>
<br />
<br />
<Button type="primary" size="large" onClick={handleSubmit}>
Submit
</Button>
</Form>
</div>
);
}
export default VideoUploadPage;
'🍞 Front-End > React' 카테고리의 다른 글
[Redux] Redux-saga를 알아보자 (0) | 2023.01.03 |
---|---|
[React] 구독하기(팔로워, 팔로우) 기능 (0) | 2022.12.29 |
[React] Expected an assignment or function call and instead saw an expression no-unused-expressions 오류 (0) | 2022.12.28 |
[React] TypeError: Cannot read properties of undefined (reading 'image') 오류 (0) | 2022.12.28 |
[React] 로그인, 로그아웃, 회원가입 구현 with Redux (0) | 2022.12.24 |