juni
TeamProject_아웃소싱 프로젝트 본문
import { useState } from 'react';
import { useAddVideo } from '../lib/supabase/videoApi';
import { searchYouTubeVideos } from '../lib/api/youtubeAPI';
import { ToastContainer, toast } from 'react-toastify';
const MainPage = () => {
const [query, setQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const addVideoMutation = useAddVideo();
const searchVideos = async (e) => {
e.preventDefault();
try {
const youtubeVideos = await searchYouTubeVideos(query);
setSearchResults(
youtubeVideos.map((video) => ({
video_title: video.snippet.title,
video_id: video.id.videoId
}))
);
} catch (error) {
console.error('Error fetching data from YouTube API:', error);
}
};
const handleAddVideo = async (video) => {
try {
if (!toast.isActive('addVideoError')) {
toast.success('해당 영상이 저장되었습니다.', {
toastId: 'addVideoError'
});
addVideoMutation.mutate(video);
}
} catch (error) {
console.error('Error adding video:', error);
}
};
return (
<div>
<ToastContainer className="mt-12" position="top-right" />
<form onSubmit={searchVideos}>
<h1 className="flex justify-center font-bold">YouTube Video Search</h1>
<div className="mb-8 flex items-center justify-center">
<input
className="mb-2 box-border rounded border-2 p-1"
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search term"
/>
<button
className="mb-2 ml-4 flex cursor-pointer items-center justify-center rounded border-2 bg-customPurple p-1 text-white no-underline hover:underline"
type="submit"
>
Search
</button>
</div>
</form>
<div className="grid grid-cols-3 gap-4">
{searchResults.map((video) => (
<div key={video.video_id}>
<h3 dangerouslySetInnerHTML={{ __html: video.video_title }} className="w-full truncate"></h3>
<div className="aspect-h-9 aspect-w-16">
<iframe
className="h-full w-full"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
<button className="mt-2 text-green-500" onClick={() => handleAddVideo(video)}>
Save
</button>
</div>
))}
</div>
</div>
);
};
export default MainPage;
import { ToastContainer, toast } from 'react-toastify';
import { useVideos, useDeleteVideo } from '../lib/supabase/videoApi';
import { Link } from 'react-router-dom';
const MyPage = () => {
const { data: videos, error: fetchError, isLoading } = useVideos();
const deleteVideoMutation = useDeleteVideo();
const handleDelete = async (id) => {
try {
toast.success('해당 영상이 삭제되었습니다.');
deleteVideoMutation.mutate(id);
} catch (error) {
console.error('Error deleting video:', error);
}
};
if (isLoading) {
return <div>Loading...</div>;
}
if (fetchError) {
return <div>Error: {fetchError.message}</div>;
}
return (
<>
<ToastContainer className="mt-12" position="top-right" />
<div className="rounded-md2 mx-auto my-0 max-w-sm bg-slate-100 p-4">
<h2 className="mb-4">프로필 수정</h2>
<div className="mb-4">
<label className="mb-2 block">닉네임</label>
<input className="box-border w-full p-2" type="text" placeholder="nickname" />
</div>
<div className="flex gap-20">
<button className="mb-2 flex w-full cursor-pointer items-center justify-center rounded border-none bg-customBlue p-2 text-white no-underline hover:underline">
프로필 업데이트
</button>
<Link
className="mb-2 flex w-full cursor-pointer items-center justify-center rounded border-none bg-customPurple p-2 text-white no-underline hover:underline"
to="/"
>
돌아가기
</Link>
</div>
</div>
<div className="mt-8">
<h1 className="flex justify-center font-bold">Saved Videos</h1>
<div className="grid grid-cols-3 gap-10">
{videos.map((video) => (
<div key={video.id}>
<h3 dangerouslySetInnerHTML={{ __html: video.video_title }} className="w-full truncate"></h3>
<div className="aspect-h-9 aspect-w-16">
<iframe
className="h-full w-full"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
<button className="mt-2 text-red-500" onClick={() => handleDelete(video.id)}>
Delete
</button>
</div>
))}
</div>
</div>
</>
);
};
export default MyPage;
import axios from 'axios';
const apiKey = import.meta.env.VITE_YOUTUBE_API_KEY;
export const searchYouTubeVideos = async (query) => {
const url = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&maxResults=9&q=${query}&key=${apiKey}`;
try {
const response = await axios.get(url);
return response.data.items;
} catch (error) {
console.error('Error fetching data from YouTube API:', error);
throw error;
}
};
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from './supabase';
export const useVideos = () => {
return useQuery({
queryKey: ['videos'],
queryFn: async () => {
const { data, error } = await supabase.from('videos').select('*').order('created_at', { ascending: false });
if (error) {
throw new Error(error.message);
}
return data;
}
});
};
export const useAddVideo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (video) => {
const { data, error } = await supabase.from('videos').insert(video);
if (error) {
throw new Error(error.message);
}
return data;
},
onSuccess: () => {
queryClient.invalidateQueries(['videos']);
}
});
};
export const useDeleteVideo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (id) => {
const { data, error } = await supabase.from('videos').delete().eq('id', id);
if (error) {
throw new Error(error.message);
}
return data;
},
onSuccess: () => {
queryClient.invalidateQueries(['videos']);
}
});
};
'프로젝트 > 미니 프로젝트' 카테고리의 다른 글
TeamProject_아웃소싱 프로젝트 (0) | 2024.06.21 |
---|---|
TeamProject_아웃소싱 프로젝트 (0) | 2024.06.19 |
버전 관리 예시 (0) | 2024.06.17 |
TeamProject_뉴스피드 프로젝트 (0) | 2024.06.10 |
TeamProject_뉴스피드 프로젝트 (0) | 2024.06.06 |