Skip to content

Commit cde27f3

Browse files
authored
Merge pull request #272 from SWM-FIRE/FIRE-768-youtube-api-공부
Fire 768 youtube api 공부
2 parents 5556766 + e2c1050 commit cde27f3

12 files changed

+313
-66
lines changed

src/api/core/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { awsRequest } from './awsLambdaAxios';
22
export { authorizationRequest, unAuthorizationRequest } from './backendAxios';
3+
export { youtubeAxios, thumbnailAxios } from './youtubeAxios';

src/api/core/youtubeAxios.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import axios from 'axios';
2+
import { API } from '../../config';
3+
4+
const youtubeAxios = axios.create({
5+
baseURL: API.YOUTUBE,
6+
});
7+
8+
// add youtube key in params
9+
youtubeAxios.interceptors.request.use((config) => {
10+
return Object.assign(config, {
11+
params: {
12+
...config.params,
13+
key: process.env.REACT_APP_YOUTUBE_KEY,
14+
},
15+
});
16+
});
17+
18+
youtubeAxios.interceptors.response.use(
19+
(response) => {
20+
return response;
21+
},
22+
(error) => {
23+
console.warn('[Axios] ', error);
24+
return Promise.reject(error);
25+
},
26+
);
27+
28+
const thumbnailAxios = axios.create({
29+
baseURL: API.YOUTUBE_THUMBNAIL,
30+
});
31+
32+
// thumbnailAxios.interceptors.request.use((config) => {
33+
// return Object.assign(config, {
34+
// params: {
35+
// ...config.params,
36+
// key: process.env.REACT_APP_YOUTUBE_KEY,
37+
// },
38+
// });
39+
// });
40+
41+
thumbnailAxios.interceptors.response.use(
42+
(response) => {
43+
return response;
44+
},
45+
(error) => {
46+
console.warn('[Axios] ', error);
47+
return Promise.reject(error);
48+
},
49+
);
50+
51+
export { youtubeAxios, thumbnailAxios };

src/api/main.ts

+26
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {
33
authorizationRequest,
44
unAuthorizationRequest,
55
awsRequest,
6+
youtubeAxios,
7+
thumbnailAxios,
68
} from './core/index';
79
import { API } from '../config';
810

@@ -139,6 +141,28 @@ const getRecords = () => {
139141
return authorizationRequest.get(API.RECORDS);
140142
};
141143

144+
/**
145+
* youtube
146+
*/
147+
148+
// search youtube video
149+
const searchYoutubeVideo = (keyword: string) => {
150+
return youtubeAxios.get(API.YOUTUBE_SEARCH, {
151+
params: {
152+
q: keyword,
153+
part: 'snippet',
154+
},
155+
});
156+
};
157+
158+
// get thumbnail img
159+
const getThumbnail = (videoId: string) => {
160+
return thumbnailAxios.get(
161+
// eslint-disable-next-line prefer-template
162+
(API.YOUTUBE_THUMBNAIL as string) + videoId + '/default.jpg',
163+
);
164+
};
165+
142166
export {
143167
getUser,
144168
getMe,
@@ -156,4 +180,6 @@ export {
156180
getFriend,
157181
getRecords,
158182
changeProfile,
183+
searchYoutubeVideo,
184+
getThumbnail,
159185
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { useEffect, useRef } from 'react';
2+
import styled from 'styled-components';
3+
import { ReactComponent as Check } from '../../../assets/svg/Check.svg';
4+
import { ReactComponent as Plus } from '../../../assets/svg/Plus.svg';
5+
import youtubeSearch from '../../../interface/youtubeSearch.interface';
6+
import { getThumbnail } from '../../../api/main';
7+
8+
export default function SearchListItem({
9+
item,
10+
isInPlaylist,
11+
}: {
12+
item: youtubeSearch;
13+
isInPlaylist: (_item: youtubeSearch) => boolean;
14+
}) {
15+
const isAdded = isInPlaylist(item);
16+
const imgRef = useRef(null);
17+
useEffect(() => {
18+
getThumbnail(item.id.videoId)
19+
.then((res) => {
20+
console.log(res);
21+
if (imgRef.current) {
22+
imgRef.current.src = res;
23+
}
24+
})
25+
.catch((err) => console.log(err));
26+
}, []);
27+
28+
return (
29+
<VideoComponent isAdded={isAdded} key={item.id.videoId}>
30+
<InnerComponent isAdded={isAdded} id={!isAdded ? 'addVideoButton' : ''}>
31+
<SvgComponent isAdded={isAdded}>
32+
{isAdded ? <Check /> : <Plus />}
33+
</SvgComponent>
34+
</InnerComponent>
35+
<Image ref={imgRef} />
36+
</VideoComponent>
37+
);
38+
}
39+
40+
const VideoComponent = styled.div<{ isAdded: boolean }>`
41+
width: 100%;
42+
height: 14rem;
43+
/* background-color: ${({ isAdded }) =>
44+
isAdded ? 'rgba(55, 65, 81, 1)' : 'rgba(55, 65, 81, 0.5)'}; */
45+
&:hover {
46+
#addVideoButton {
47+
display: grid;
48+
}
49+
}
50+
#addVideoButton {
51+
cursor: pointer;
52+
}
53+
`;
54+
55+
const InnerComponent = styled.div<{ isAdded: boolean }>`
56+
width: 100%;
57+
height: 100%;
58+
display: ${({ isAdded }) => (isAdded ? 'flex' : 'none')};
59+
align-items: center;
60+
justify-content: center;
61+
background-color: rgba(0, 0, 0, 0.5);
62+
transition: all 0.3s ease-in-out;
63+
`;
64+
65+
const SvgComponent = styled.div<{ isAdded: boolean }>`
66+
width: 5rem;
67+
height: 5rem;
68+
display: flex;
69+
justify-content: center;
70+
align-items: center;
71+
background-color: ${({ isAdded }) => (isAdded ? '#00c113' : '#3C3C3C')};
72+
border-radius: 50%;
73+
svg {
74+
width: 60%;
75+
height: 60%;
76+
}
77+
`;
78+
79+
const Image = styled.image`
80+
width: 100%;
81+
height: 100%;
82+
`;
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
1-
import React, { useState } from 'react';
1+
import React from 'react';
22
import styled from 'styled-components';
33
import YoutubeModalHeader from './YoutubeModalHeader';
44
import YoutubeModalInput from './YoutubeModalInput';
5+
import YoutubeModalPlaying from './YoutubeModalPlaying';
6+
import YoutubeModalPlaylist from './YoutubeModalSearchList';
57

6-
export default function YoutubeModal({ toggle }) {
7-
const [input, setInput] = useState('');
8-
console.log(input);
9-
8+
export default function YoutubeModal({
9+
toggle,
10+
searchList,
11+
setSearchList,
12+
isInPlaylist,
13+
}) {
14+
console.log('youtubeModal');
1015
return (
1116
<Component>
1217
<YoutubeModalHeader toggle={toggle} />
13-
<YoutubeModalInput setInput={setInput} />
14-
<Playing>
15-
<Video />
16-
</Playing>
17-
<PlayList>
18-
<Video />
19-
<Video />
20-
<Video />
21-
<Video />
22-
<Video />
23-
<Video />
24-
</PlayList>
18+
<YoutubeModalInput setSearchList={setSearchList} />
19+
<YoutubeModalPlaying />
20+
<YoutubeModalPlaylist
21+
searchList={searchList}
22+
isInPlaylist={isInPlaylist}
23+
/>
2524
</Component>
2625
);
2726
}
@@ -42,32 +41,3 @@ const Component = styled.div`
4241
box-shadow: 0px 4px 59px rgba(50, 50, 71, 0.3);
4342
font-family: IBMPlexSansKRRegular;
4443
`;
45-
46-
const Playing = styled.div`
47-
width: 100%;
48-
height: 30rem;
49-
border-top: 1px solid rgba(55, 65, 81, 1);
50-
padding-top: 1rem;
51-
`;
52-
53-
const Video = styled.video`
54-
background-color: lightGray;
55-
width: 100%;
56-
height: 28rem;
57-
`;
58-
59-
const PlayList = styled.div`
60-
display: grid;
61-
grid-template-columns: repeat(2, 1fr);
62-
grid-gap: 1rem;
63-
width: 100%;
64-
flex-shrink: 1;
65-
overflow: scroll;
66-
margin-top: 1rem;
67-
video {
68-
height: 14rem;
69-
}
70-
::-webkit-scrollbar {
71-
display: none;
72-
}
73-
`;

src/components/room/youtubeModal/YoutubeModalInput.tsx

+20-8
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,38 @@
11
import React, { useState } from 'react';
22
import styled from 'styled-components';
33
import { ReactComponent as Search } from '../../../assets/svg/Search.svg';
4+
import { searchYoutubeVideo } from '../../../api/main';
45

5-
export default function YoutubeModalInput({ setInput }) {
6+
export default function YoutubeModalInput({ setSearchList }) {
67
const [newInput, setNewInput] = useState('');
78
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
89
setNewInput(event.target.value);
9-
setInput(event.target.value);
10+
};
11+
12+
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
13+
event.preventDefault();
14+
searchYoutubeVideo(newInput)
15+
.then((res) => {
16+
console.log(res.data);
17+
setSearchList(res.data.items);
18+
setNewInput('');
19+
})
20+
.catch((err) => {
21+
console.log(err);
22+
});
1023
};
1124

1225
return (
13-
<InputComponent>
26+
<InputComponent onSubmit={onSubmit}>
1427
<SearchInput value={newInput} onChange={onChange} placeholder="검색" />
15-
<SearchSvg>
28+
<SearchButton>
1629
<Search />
17-
</SearchSvg>
30+
</SearchButton>
1831
</InputComponent>
1932
);
2033
}
2134

22-
const InputComponent = styled.div`
35+
const InputComponent = styled.form`
2336
width: 100%;
2437
height: 4.8rem;
2538
display: flex;
@@ -45,14 +58,13 @@ const SearchInput = styled.input`
4558
}
4659
`;
4760

48-
const SearchSvg = styled.button`
61+
const SearchButton = styled.button`
4962
width: 4.2rem;
5063
height: 4rem;
5164
display: flex;
5265
align-items: center;
5366
justify-content: center;
5467
cursor: pointer;
55-
5668
svg {
5769
width: 40%;
5870
height: 40%;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import styled from 'styled-components';
2+
3+
export default function YoutubeModalPlaying() {
4+
return (
5+
<Playing>
6+
<Video />
7+
</Playing>
8+
);
9+
}
10+
const Playing = styled.div`
11+
width: 100%;
12+
height: 30rem;
13+
border-top: 1px solid rgba(55, 65, 81, 1);
14+
padding-top: 1rem;
15+
`;
16+
17+
const Video = styled.video`
18+
background-color: lightGray;
19+
width: 100%;
20+
height: 100%;
21+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import styled from 'styled-components';
2+
import youtubeSearch from '../../../interface/youtubeSearch.interface';
3+
import SearchListItem from './SearchListItem';
4+
5+
export default function YoutubeModalSearchList({ searchList, isInPlaylist }) {
6+
console.log(searchList);
7+
8+
return (
9+
<SearchList>
10+
{searchList.map((item: youtubeSearch) => (
11+
<SearchListItem
12+
key={item.id.videoId}
13+
item={item}
14+
isInPlaylist={isInPlaylist}
15+
/>
16+
))}
17+
</SearchList>
18+
);
19+
}
20+
21+
const SearchList = styled.div`
22+
display: grid;
23+
grid-template-columns: repeat(2, 1fr);
24+
grid-gap: 1rem;
25+
width: 100%;
26+
flex-shrink: 1;
27+
overflow: scroll;
28+
margin-top: 1rem;
29+
video {
30+
height: 14rem;
31+
}
32+
::-webkit-scrollbar {
33+
display: none;
34+
}
35+
`;

src/config.ts

+6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const GITHUB = '/auth/github/';
1313
const LAMBDA_URL = process.env.REACT_APP_LAMBDA_INVITE;
1414
const INVITE = '/invite/';
1515
const FRIEND = '/friendships';
16+
const YOUTUBE = 'https://www.googleapis.com/youtube/v3';
17+
const THUMBNAIL = 'https://img.youtube.com/vi/';
18+
const SEARCH = '/search';
1619

1720
export const API = {
1821
BASE_URL: `${BASE_URL}`,
@@ -28,4 +31,7 @@ export const API = {
2831
GITHUB: `${BASE_URL}${API_VER}${GITHUB}`,
2932
INVITE: `${LAMBDA_URL}${INVITE}`,
3033
FRIEND: `${BASE_URL}${API_VER}${FRIEND}`,
34+
YOUTUBE: `${YOUTUBE}`,
35+
YOUTUBE_SEARCH: `${YOUTUBE}${SEARCH}`,
36+
YOUTUBE_THUMBNAIL: `${THUMBNAIL}`,
3137
};

0 commit comments

Comments
 (0)