diff --git a/src/components/youtube-playlist-item/youtube-playlist-item.jsx b/src/components/youtube-playlist-item/youtube-playlist-item.jsx new file mode 100644 index 000000000..74ae82456 --- /dev/null +++ b/src/components/youtube-playlist-item/youtube-playlist-item.jsx @@ -0,0 +1,61 @@ +import React, {useEffect, useState} from 'react'; +import PropTypes from 'prop-types'; + +import {FormattedMessage} from 'react-intl'; +import api from '../../lib/api'; +import {YoutubeVideoButton} from '../youtube-video-button/youtube-video-button.jsx'; +import Spinner from '../spinner/spinner.jsx'; + +import './youtube-playlist-item.scss'; + +export const YoutubePlaylistItem = ({playlistRequestUri, playlistTitleId, onSelectedVideo}) => { + const [loading, setLoading] = useState(true); + const [playlistVideos, setPlaylistVideos] = useState([]); + + useEffect(() => { + api({ + host: process.env.ROOT_URL, + method: 'GET', + uri: playlistRequestUri, + headers: { + 'Content-Type': 'application/json' + } + }, (_err, body, res) => { + setLoading(false); + if (res.statusCode === 200) { + setPlaylistVideos(body); + } + }); + }, []); + + return ( +
+
+ +
+ {loading ? ( + + ) : ( +
+ {playlistVideos + .map(video => ( + + ))} +
+ )} +
+ ); +}; + +YoutubePlaylistItem.propTypes = { + playlistRequestUri: PropTypes.string, + playlistTitleId: PropTypes.string, + onSelectedVideo: PropTypes.func +}; diff --git a/src/components/youtube-playlist-item/youtube-playlist-item.scss b/src/components/youtube-playlist-item/youtube-playlist-item.scss new file mode 100644 index 000000000..91f03c4b1 --- /dev/null +++ b/src/components/youtube-playlist-item/youtube-playlist-item.scss @@ -0,0 +1,26 @@ +.playlist { + display: flex; + flex-direction: column; + gap: 1rem; + + .playlist-title { + display: flex; + justify-content: start; + font-size: 2rem; + font-weight: 700; + line-height: 2.5rem; + } + + .spinner { + width: 100%; + height: 50px; + } + + .playlist-videos { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(13.125rem, auto)); + justify-content: space-around; + align-items: start; + gap: 1rem; + } +} \ No newline at end of file diff --git a/src/components/youtube-video-button/youtube-video-button.jsx b/src/components/youtube-video-button/youtube-video-button.jsx new file mode 100644 index 000000000..5868b77b6 --- /dev/null +++ b/src/components/youtube-video-button/youtube-video-button.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import './youtube-video-button.scss'; + +const parseViewCount = viewCount => + parseInt(viewCount, 10).toLocaleString('en-US', { + notation: 'compact', + compactDisplay: 'short' + }); + +export const YoutubeVideoButton = ({onSelectedVideo, ...videoData}) => ( +
onSelectedVideo(videoData.videoId)} + > +
+ +
{videoData.length}
+
+
+
{videoData.title}
+
+
{videoData.channel}
+
+ {`${parseViewCount(videoData.views)} ยท ${videoData.uploadTime}`} +
+
+ {videoData.hasCC && } +
+
+); + +YoutubeVideoButton.propTypes = { + videoId: PropTypes.string, + title: PropTypes.string, + thumbnail: PropTypes.string, + channel: PropTypes.string, + uploadTime: PropTypes.string, + length: PropTypes.string, + views: PropTypes.string, + hasCC: PropTypes.bool, + onSelectedVideo: PropTypes.func +}; diff --git a/src/components/youtube-video-button/youtube-video-button.scss b/src/components/youtube-video-button/youtube-video-button.scss new file mode 100644 index 000000000..9ff0a6502 --- /dev/null +++ b/src/components/youtube-video-button/youtube-video-button.scss @@ -0,0 +1,70 @@ +@import "../../colors"; + +.youtbe-video-button { + display: flex; + flex-direction: column; + gap: 0.75rem; + + width: 13.125rem; + + .thumbnail { + position: relative; + width: 100%; + + img { + position: relative; + width: 100%; + border-radius: 12px; + } + + .duration { + position: absolute; + z-index: 1; + bottom : 0.5rem; + right: 0; + + margin: 0.5rem; + padding: 0.25rem; + border-radius: 4px; + background-color: $overlay-gray; + color: $type-white; + + font-size: 0.75rem; + font-weight: 500; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + } + } + + .video-info { + margin-right: 1.5rem; + text-align: start; + + .video-title { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + line-height: 1.2rem; + max-height: 2.4rem; + + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 600; + font-size: 0.875rem; + color: black; + } + + .video-metadata { + margin: 0.5rem 4rem 0.25rem 0; + + color: $ui-dark-gray; + font-size: 0.75rem; + font-weight: 500; + line-height: 1.125rem; + } + } + + &:hover { + cursor: pointer; + } +} \ No newline at end of file diff --git a/src/components/youtube-video-modal/youtube-video-modal.jsx b/src/components/youtube-video-modal/youtube-video-modal.jsx new file mode 100644 index 000000000..60d503628 --- /dev/null +++ b/src/components/youtube-video-modal/youtube-video-modal.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import ReactModal from 'react-modal'; +import PropTypes from 'prop-types'; +import Button from '../forms/button.jsx'; +const classNames = require('classnames'); + +import './youtube-video-modal.scss'; + +export const YoutubeVideoModal = ({videoId, onClose = () => {}, className}) => { + if (!videoId) return null; + return ( + +
+
+