Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post video #19

Merged
merged 9 commits into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 97 additions & 14 deletions src/api/addVideo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import axios from 'axios';

export type Video = {
id: string,
snippet: {
title: string,
description: string,
}
title: string,
description: string,
};

export type PlayList = {
Expand All @@ -22,6 +20,7 @@ type VideosResponse = {
totalResults: number,
resultsPerPage: number,
},
nextPageToken: string | undefined,
};

type PlayListsResponse = {
Expand All @@ -30,9 +29,47 @@ type PlayListsResponse = {
totalResults: number,
resultsPerPage: number,
},
nextPageToken: string | undefined,
};

type PlaylistVideo = {
snippet: {
title: string,
description: string,
resourceId: {
videoId: string,
},
},
};

type LikeVideo = {
id: string,
snippet: {
title: string,
description: string,
},
};

type SearchVideo = {
id: {
videoId: string,
},
snippet: {
title: string,
description: string,
},
};

type AxiosVideoResponse<T> = {
items: Array<T>,
pageInfo: {
totalResults: number,
resultsPerPage: number,
},
nextPageToken: string | undefined,
};

const getPlayLists = async (pageToken = ""): Promise<PlayListsResponse> => {
const getPlayLists = async (pageToken: string|undefined = undefined): Promise<PlayListsResponse> => {
const res = await axios.request<PlayListsResponse>({
baseURL: API_URL,
url: '/v1/contents/playlists',
Expand All @@ -44,8 +81,8 @@ const getPlayLists = async (pageToken = ""): Promise<PlayListsResponse> => {
return res.data;
};

const getPlayList = async (playListId: string, pageToken = ""): Promise<VideosResponse> => {
const res = await axios.request<VideosResponse>({
const getPlayList = async (playListId: string, pageToken: string|undefined = undefined): Promise<VideosResponse> => {
const res = await axios.request<AxiosVideoResponse<PlaylistVideo>>({
baseURL: API_URL,
url: '/v1/contents/playlist',
method: 'get',
Expand All @@ -54,23 +91,43 @@ const getPlayList = async (playListId: string, pageToken = ""): Promise<VideosRe
pageToken: pageToken,
},
});
return res.data;

const videos = res.data.items.map(val => ({
id: val.snippet.resourceId.videoId,
title: val.snippet.title,
description: val.snippet.description,
}));
return {
items: videos,
pageInfo: res.data.pageInfo,
nextPageToken: res.data.nextPageToken,
}
};

const getLikeList = async (pageToken = ""): Promise<VideosResponse> => {
const res = await axios.request<VideosResponse>({
const getLikeList = async (pageToken: string|undefined = undefined): Promise<VideosResponse> => {
const res = await axios.request<AxiosVideoResponse<LikeVideo>>({
baseURL: API_URL,
url: '/v1/contents/likelist',
method: 'get',
params: {
pageToken: pageToken,
},
});
return res.data;

const videos = res.data.items.map(val => ({
id: val.id,
title: val.snippet.title,
description: val.snippet.description,
}));
return {
items: videos,
pageInfo: res.data.pageInfo,
nextPageToken: res.data.nextPageToken,
}
};

const getSearchList = async (keyword: string, pageToken = ""): Promise<VideosResponse> => {
const res = await axios.request<VideosResponse>({
const getSearchList = async (keyword: string, pageToken: string|undefined = undefined): Promise<VideosResponse> => {
const res = await axios.request<AxiosVideoResponse<SearchVideo>>({
baseURL: API_URL,
url: '/v1/contents/search',
method: 'get',
Expand All @@ -79,12 +136,38 @@ const getSearchList = async (keyword: string, pageToken = ""): Promise<VideosRes
pageToken: pageToken,
},
});
return res.data;

const videos = res.data.items.map(val => ({
id: val.id.videoId,
title: val.snippet.title,
description: val.snippet.description,
}));
return {
items: videos,
pageInfo: res.data.pageInfo,
nextPageToken: res.data.nextPageToken,
}
};

const postVideo = async (videoId: string, title: string, description: string, tags: Array<string>): Promise<boolean> => {
const res = await axios.request({
baseURL: API_URL,
url: '/v1/post/video',
method: 'post',
data: {
videoId: videoId,
title: title,
description: description,
tags: tags,
},
});
return res.status == 200;
}

export {
getPlayLists,
getPlayList,
getLikeList,
getSearchList,
postVideo,
};
5 changes: 5 additions & 0 deletions src/component/content/Add/AddVideo.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@
.add-video-submit-button {
font-size: 20px;
width: 100px;
}

.add-video-youtube-video {
width: 100%;
height: 100%;
}
74 changes: 65 additions & 9 deletions src/component/content/Add/AddVideo.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
import React, { useEffect, useState } from "react";
import AddVideoHook from "../../../hooks/AddVideo";

import AddVideoModal from "./AddVideoModal";
import GetLogin from "../../../hooks/GetLogin";
import { FaTimes } from 'react-icons/fa';
import "./AddVideo.css";
import { Redirect } from "react-router-dom";
import { postVideo } from "../../../api/addVideo";
import { maxTagCount, maxTagLength, titleMaxLength, titleMinLength, descriptionMaxLength, tagAllowedPattern } from "../../../constant/Addvideo";

const getYoutubeThumbnailUrl = (id: string) => {
return `https://img.youtube.com/vi/${id}/0.jpg`;
const getYoutubeiframe = (id: string) => (
<iframe src={`https://www.youtube.com/embed/${id}`}
frameBorder='0'
allow='autoplay; encrypted-media'
allowFullScreen
title='video'
className='add-video-youtube-video'
/>
);
const setYoutubeId = (id: string) => {
return `https://www.youtube.com/watch?v=${id}`
}
const maxTagCount = 5;

const AddVideo: React.FC = () => {
const { userInfo } = GetLogin();
const { id, url, title, description, tags, init, setUrl, setTitle, setDescription, addTag, deleteTag } = AddVideoHook();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 addTag 한 이후에 다른페이지를 갔다가 다시 영상 추가 페이지로 이동할 시, tag들만 그대로 남아있는 문제가 있습니다. 영상 등록 성공 후에는 모든 제목, 영상 url, tag, 설명 등을 지워주는 로직을 추가하면 좋을 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정했습니다.

const [tag, setTag] = useState("");

// mount될 때만 init함수가 실행되도록 하고 싶어서 lint warning을 없앴습니다.
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(init, []);

// TODO: implement debounce
if(userInfo == null) {
alert("로그인이 필요한 페이지입니다.");
return <Redirect to="/"/>;
}

const handleUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setUrl(e.target.value);
};
Expand All @@ -31,7 +47,7 @@ const AddVideo: React.FC = () => {

// is memoization needed?
const thumbnailView = (id != "") ? (<div className="add-video-thumbnail-container">
<img src={getYoutubeThumbnailUrl(id)} />
{getYoutubeiframe(id)}
</div>) : (<div className="add-video-thumbnail-container">
<div>
<FaTimes className="add-video-thumbnail-times" />
Expand Down Expand Up @@ -72,18 +88,31 @@ const AddVideo: React.FC = () => {
if(tag == "") {
return;
}
if(!tagAllowedPattern.test(tag)) {
alert("태그에는 한글, 영어, 숫자만 입력 가능합니다.");
return;
}
if(tag.length >= maxTagLength) {
alert(`한 태그의 길이는 ${maxTagLength}자까지 가능합니다.`);
return;
}
if(tags.some((val) => (val==tag))) {
alert("같은이름의 태그가 존재합니다.");
return;
}
if(tags.length >= maxTagCount) {
alert("태그는 5개까지 등록이 가능합니다.");
alert(`태그는 ${maxTagCount}개까지 등록이 가능합니다.`);
return;
}
addTag(tag);
setTag("");
}
}

const setVideo = (id: string) => {
setUrl(setYoutubeId(id));
}

const tagsInput = (
<div className="add-video-input-container">
<span className="badge bg-primary">태그</span>
Expand All @@ -97,8 +126,35 @@ const AddVideo: React.FC = () => {
</div>);
};

const handleSubmit = async (): Promise<void> => {
if(id == "") {
alert("정상적인 유튜브 동영상을 입력해주세요.");
return;
}
if(title == "") {
alert("제목이 없습니다.");
return;
}
if(title.length < titleMinLength || title.length > titleMaxLength) {
alert(`제목은 ${titleMinLength}~${titleMaxLength}자까지 가능합니다.`);
return;
}
if(description.length > descriptionMaxLength) {
alert(`설명은 ${descriptionMaxLength}자까지 가능합니다.`);
return;
}

const ok = await postVideo(id, title, description, tags);
if(!ok) {
alert("알 수 없는 에러가 발생하였습니다.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API 에서 실패 원인을 내려주긴하는데 실패 원인을 보여줄 수 있으면 좋을 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 해당 API에서는 input validation을 통과하지 못하면 400 에러가 나는데, client-side에서도 validation을 체크한 상태여서 현재는 딱히 에러를 표시할게 없습니다.

이슈 등록해두고 추후에 에러코드가 다양해지면 수정하겠습니다.

return;
}

alert("gooood");
}

return (<div className="add-video-container">
<AddVideoModal />
<AddVideoModal setVideo={setVideo}/>
{urlInput}
{thumbnailView}
{titleInput}
Expand All @@ -107,7 +163,7 @@ const AddVideo: React.FC = () => {
<div className="add-video-tags-container">
{tags.map((tag, idx) => tagInput(tag, idx))}
</div>
<button type="button" className="btn btn-success add-video-submit-button">등록</button>
<button type="button" className="btn btn-success add-video-submit-button" onClick={handleSubmit}>등록</button>
</div>);
}

Expand Down
Loading