├── .eslintrc.json ├── .github └── FUNDING.yml ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── index.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg ├── thirteen.svg └── vercel.svg ├── src ├── Components │ ├── Converter │ │ ├── Converter.tsx │ │ ├── SpotifytoYT │ │ │ ├── CreateYTPlaylist.tsx │ │ │ ├── SearchSongsOnYT.tsx │ │ │ └── SpotifytoYT.tsx │ │ └── YTtoSpotify │ │ │ ├── CreateSpotifyPlaylist.tsx │ │ │ ├── SearchSongsOnSpotify.tsx │ │ │ └── YTtoSpotify.tsx │ ├── Onboarding │ │ ├── GoogleLogin.tsx │ │ ├── LandingPage.tsx │ │ └── SpotifyLogin.tsx │ ├── SignOut.tsx │ ├── resources │ │ └── images │ │ │ ├── g-logo.png │ │ │ └── yt-logo.png │ └── utils │ │ └── utilsFunctions.ts ├── app │ ├── contextProvider.tsx │ ├── converter │ │ └── page.tsx │ ├── globals.css │ ├── googleLogin │ │ └── page.tsx │ ├── layout.tsx │ ├── page.tsx │ ├── privacyPolicy │ │ └── page.tsx │ ├── sessionProvider.tsx │ └── spotifyLogin │ │ └── page.tsx ├── hooks │ ├── useRefreshSpotifyToken.tsx │ └── useSpotify.tsx └── pages │ └── api │ ├── auth │ └── [...nextauth].js │ └── spotify │ └── getSpotifySongs │ └── index.ts ├── tailwind.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: anonthedev 2 | buy_me_a_coffee: anonthedev 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules\\typescript\\lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 20 | 21 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 22 | 23 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 24 | 25 | ## Learn More 26 | 27 | To learn more about Next.js, take a look at the following resources: 28 | 29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 31 | 32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 33 | 34 | ## Deploy on Vercel 35 | 36 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 37 | 38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 39 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "next-auth/react" -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | appDir: true, 5 | }, 6 | } 7 | 8 | module.exports = nextConfig 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youify-next", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@types/node": "18.15.5", 13 | "@types/react": "18.0.28", 14 | "@types/react-dom": "18.0.11", 15 | "@vercel/analytics": "^1.0.0", 16 | "axios": "^1.3.6", 17 | "eslint": "8.36.0", 18 | "eslint-config-next": "14.1.0", 19 | "next": "13.2.4", 20 | "next-auth": "^4.24.5", 21 | "react": "18.2.0", 22 | "react-dom": "18.2.0", 23 | "spotify-web-api-node": "^5.0.2", 24 | "typescript": "5.3.3" 25 | }, 26 | "devDependencies": { 27 | "@tailwindcss/typography": "^0.5.9", 28 | "@types/spotify-web-api-node": "^5.0.7", 29 | "autoprefixer": "^10.4.14", 30 | "postcss": "^8.4.23", 31 | "tailwindcss": "^3.3.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/thirteen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Components/Converter/Converter.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useContext, useEffect } from "react"; 4 | import { GlobalContext } from "@/app/contextProvider"; 5 | import YTtoSpotify from "./YTtoSpotify/YTtoSpotify"; 6 | import SpotifytoYT from "./SpotifytoYT/SpotifytoYT"; 7 | import axios from "axios"; 8 | import useRefreshSpotifyToken from "@/hooks/useRefreshSpotifyToken"; 9 | import SignOut from "../SignOut"; 10 | import { useSession } from "next-auth/react"; 11 | import { redirect } from "next/navigation"; 12 | 13 | export default function Converter() { 14 | const { status, data: session } = useSession(); 15 | const currentDate = Date.now(); 16 | const refreshSpotifyToken = useRefreshSpotifyToken(); 17 | const [WhatToWhat, setWhatToWhat] = useState(["YouTube", "Spotify"]); 18 | const [From, setFrom] = useState("YouTube"); 19 | const [To, setTo] = useState("Spotify"); 20 | const context = useContext(GlobalContext); 21 | 22 | const LSAvailable = typeof window !== "undefined"; 23 | const spotifyToken = LSAvailable 24 | ? localStorage.getItem("spotifyAccessToken") 25 | : {}; 26 | context.setSpotifyGlobalToken(spotifyToken); 27 | 28 | useEffect(() => { 29 | if (LSAvailable && session) { 30 | if ( 31 | status === "unauthenticated" && 32 | !localStorage.getItem("spotifyAccessToken") 33 | ) { 34 | redirect("/"); 35 | } 36 | } 37 | }); 38 | 39 | useEffect(() => { 40 | function getUserIdSpotify() { 41 | var userParams = { 42 | method: "GET", 43 | headers: { 44 | Accept: "application/json", 45 | "Content-Type": "application/json", 46 | Authorization: "Bearer " + localStorage.getItem("spotifyAccessToken"), 47 | }, 48 | }; 49 | 50 | axios 51 | .get("https://api.spotify.com/v1/me", userParams) 52 | .then((resp) => { 53 | // console.log(resp.data.id); 54 | context.setUserId(resp.data.id); 55 | // console.log("X"); 56 | // navigate("/googleLogin"); 57 | }) 58 | .catch((err) => { 59 | console.log(err); 60 | }); 61 | } 62 | if (LSAvailable) { 63 | const spotifyTokenExpireDate = new Date( 64 | localStorage.getItem("spotifyTokenExpire")! 65 | ); 66 | // console.log(typeof currentDate) 67 | if (new Date(currentDate) >= spotifyTokenExpireDate) { 68 | refreshSpotifyToken; 69 | } else if (new Date(currentDate) < spotifyTokenExpireDate) { 70 | getUserIdSpotify(); 71 | } else { 72 | getUserIdSpotify(); 73 | } 74 | } 75 | }); 76 | 77 | // console.log(WhatToWhat); 78 | return ( 79 |
80 |
81 | 82 |
83 |
84 |
85 |
86 |
87 | {From} 88 |
89 | TO 90 |
91 | {To} 92 |
93 |
94 |
{ 96 | setWhatToWhat(WhatToWhat.reverse()); 97 | setFrom(WhatToWhat[0]); 98 | setTo(WhatToWhat[1]); 99 | context.setPlaylistTracks([]); 100 | }} 101 | className="text-3xl md:text-xl cursor-pointer" 102 | > 103 | {String.fromCodePoint(parseInt("21C5", 16))} 104 |
105 |
106 | {WhatToWhat[0] === "Spotify" ? : } 107 |
108 | 113 |
114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /src/Components/Converter/SpotifytoYT/CreateYTPlaylist.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useContext, useEffect } from "react"; 2 | import { GlobalContext } from "@/app/contextProvider"; 3 | 4 | export default function CreateYTPlaylist() { 5 | const context = useContext(GlobalContext); 6 | const videoIds: string[] = []; 7 | context.playlistTracks 8 | ? context.playlistTracks.map((track: any) => { 9 | // console.log(track.items[0].id.videoId); 10 | videoIds.push(track.items[0].id.videoId); 11 | }) 12 | : ""; 13 | 14 | const wait = "Wait..."; 15 | const creating = "Creating..."; 16 | const created = "Playlist Created"; 17 | const wrong = "Try again"; 18 | 19 | const [playlistCompletion, setPlaylistCompletion] = useState(""); 20 | 21 | useEffect(() => { 22 | if (playlistCompletion === created) { 23 | setTimeout(() => { 24 | setPlaylistCompletion("Create Playlist"); 25 | }, 3000); 26 | } else if (playlistCompletion === wrong) { 27 | setTimeout(() => { 28 | setPlaylistCompletion("Create Playlist"); 29 | }, 3000); 30 | } 31 | }, [playlistCompletion]); 32 | 33 | function addTracksToPlaylist(videoId: string, playlistId: string) { 34 | fetch( 35 | ` https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails,id,snippet,status`, 36 | { 37 | method: "POST", 38 | headers: { 39 | "Content-Type": "application/json", 40 | Authorization: "Bearer " + context.globalGoogleToken, 41 | }, 42 | body: JSON.stringify({ 43 | snippet: { 44 | playlistId: playlistId, 45 | resourceId: { 46 | kind: "youtube#video", 47 | videoId: videoId, 48 | }, 49 | }, 50 | }), 51 | } 52 | ); 53 | // console.log(videoId, playlistId) 54 | } 55 | 56 | const callCreatePlaylist = (e: any) => { 57 | e.preventDefault(); 58 | setPlaylistCompletion(wait); 59 | setTimeout(() => { 60 | if ( 61 | context.globalGoogleToken && 62 | context.PlaylistName && 63 | videoIds.length > 0 64 | ) { 65 | createPlaylist(); 66 | } else { 67 | setPlaylistCompletion(wrong); 68 | } 69 | }, 2000); 70 | }; 71 | 72 | async function createPlaylist() { 73 | setPlaylistCompletion(creating); 74 | var createPlaylistParams = { 75 | method: "POST", 76 | headers: { 77 | Accept: "application/json", 78 | "Content-Type": "application/json", 79 | Authorization: "Bearer " + context.globalGoogleToken, 80 | }, 81 | body: JSON.stringify({ 82 | snippet: { 83 | title: context.PlaylistName, 84 | description: context.PlaylistDesc, 85 | }, 86 | status: { 87 | privacyStatus: "public", 88 | }, 89 | }), 90 | }; 91 | 92 | const URL = `https://www.googleapis.com/youtube/v3/playlists?part=id,snippet,status`; 93 | 94 | fetch(URL, createPlaylistParams) 95 | .then((data) => { 96 | return data.json(); 97 | }) 98 | .then((resp) => { 99 | // console.log(resp); 100 | return resp.id; 101 | }) 102 | .then((id) => { 103 | videoIds.map((singleVideoId, index) => { 104 | setTimeout(() => { 105 | addTracksToPlaylist(singleVideoId, id); 106 | }, 1200 * index); 107 | }); 108 | }) 109 | .then(() => { 110 | setPlaylistCompletion(created); 111 | }) 112 | .catch(() => { 113 | setPlaylistCompletion("Try again"); 114 | }); 115 | } 116 | 117 | return ( 118 |
119 | 142 |
143 | ); 144 | } 145 | -------------------------------------------------------------------------------- /src/Components/Converter/SpotifytoYT/SearchSongsOnYT.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import { useState, useContext, useEffect, useRef } from "react"; 3 | import { GlobalContext } from "@/app/contextProvider"; 4 | 5 | export default function SearchSongsOnYT({ title }: { title: any }) { 6 | const [Alltracks, setTracks] = useState([]); 7 | const context = useContext(GlobalContext); 8 | 9 | const playlistNameRef = useRef(null); 10 | 11 | useEffect(() => { 12 | const apiKey = process.env.NEXT_PUBLIC_GOOGLE_API_KEY; 13 | const arr: any[] = []; 14 | // console.log(localStorage.getItem("spotifyAccessToken")) 15 | setTracks([]); 16 | title 17 | ? title.map(async (singleTitle: any) => { 18 | const URL = `https://www.googleapis.com/youtube/v3/search?key=${apiKey}&q=${ 19 | singleTitle.track.name.replace(" ", "%20") + 20 | "%20" + 21 | singleTitle.track.artists[0].name.replace(" ", "%20") + 22 | "%20" + 23 | "song topic" 24 | }&type=video&part=snippet&maxResults=1`; 25 | // console.log(URL); 26 | await fetch(URL) 27 | .then((data) => data.json()) 28 | .then((resp) => { 29 | // console.log(resp); 30 | arr.push(resp); 31 | setTracks((Alltracks) => [...Alltracks, resp]); 32 | }); 33 | setTimeout(() => { 34 | context.setPlaylistTracks(arr); 35 | }, 1500); 36 | }) 37 | : ""; 38 | }, [title]); 39 | 40 | // console.log(Alltracks.length != 0 ? Alltracks : ""); 41 | // console.log(context.playlistTracks.length != 0 ? context.playlistTracks : ""); 42 | 43 | return ( 44 |
45 |
46 | { 56 | context.setPlaylistName(e.target.value); 57 | }} 58 | /> 59 |