├── .gitignore ├── assets ├── add.svg ├── album.jpg ├── avatar.jpg ├── chevronLeft.svg ├── chevronRight.svg ├── disc.svg ├── download.svg ├── friend.svg ├── heart.svg ├── home.svg ├── more.svg ├── next.svg ├── pause.svg ├── play.svg ├── playRounded.svg ├── playlist.svg ├── previous.svg ├── repeat.svg ├── search.svg ├── shuffle.svg ├── speaker.svg └── time.svg ├── components ├── UploadModal.js ├── activity.js ├── activityCard.js ├── header.js ├── login.js ├── nav.js ├── navLink.js ├── payment.js ├── playerControls.js ├── playlist.js ├── table │ └── tableRow.js └── uploadButton.js ├── context ├── WalletConnectionProvider.js └── context.js ├── data ├── activities.js └── songs.js ├── hooks └── useSpotify.js ├── next.config.js ├── package.json ├── pages ├── _app.js ├── api │ └── upload_music.js ├── homepage.js └── index.js ├── postcss.config.js ├── public ├── assets │ ├── add.svg │ ├── album.jpg │ ├── avatar.jpg │ ├── chevronLeft.svg │ ├── chevronRight.svg │ ├── disc.svg │ ├── download.svg │ ├── friend.svg │ ├── heart.svg │ ├── home.svg │ ├── more.svg │ ├── next.svg │ ├── play.svg │ ├── playRounded.svg │ ├── playlist.svg │ ├── previous.svg │ ├── repeat.svg │ ├── search.svg │ ├── shuffle.svg │ ├── speaker.svg │ └── time.svg ├── favicon.ico └── vercel.svg ├── readme.md ├── styles ├── Home.module.css ├── UploadModal.module.css └── globals.css ├── tailwind.config.js ├── utils ├── const.js ├── spotify.json └── utils.js └── wallet.json /.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 | out/ 13 | ./next 14 | .next/ 15 | .next 16 | 17 | # production 18 | build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | .pnpm-debug.log* 29 | 30 | # local env files 31 | .env.local 32 | .env.development.local 33 | .env.test.local 34 | .env.production.local 35 | 36 | # vercel 37 | .vercel 38 | 39 | # snyk code quality 40 | .dccache 41 | 42 | # lock files 43 | yarn.lock 44 | package-lock.json 45 | 46 | target 47 | 48 | *.wav -------------------------------------------------------------------------------- /assets/add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/album.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverProgrammers/spotify-clone-blockchain/afe6e4409227b097d10c65323b8e87b2b7eece4b/assets/album.jpg -------------------------------------------------------------------------------- /assets/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverProgrammers/spotify-clone-blockchain/afe6e4409227b097d10c65323b8e87b2b7eece4b/assets/avatar.jpg -------------------------------------------------------------------------------- /assets/chevronLeft.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/chevronRight.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/disc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/friend.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/more.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/playRounded.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/playlist.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/previous.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/repeat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/shuffle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/speaker.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/time.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/UploadModal.js: -------------------------------------------------------------------------------- 1 | import style from '../styles/UploadModal.module.css' 2 | import axios from 'axios' 3 | 4 | const UploadModal = ({ 5 | description: title, 6 | musicUrl, 7 | newMusic, 8 | setTitle, 9 | setMusicUrl, 10 | setShowUploadMusic, 11 | }) => { 12 | const toBase64 = file => 13 | new Promise((resolve, reject) => { 14 | const reader = new FileReader() 15 | reader.readAsDataURL(file) 16 | reader.onload = () => resolve(reader.result) 17 | reader.onerror = error => reject(error) 18 | }) 19 | 20 | const uploadClicked = async () => { 21 | var files = document.querySelector('#music-file') 22 | 23 | if (files.files.length == 0) return 24 | 25 | const base64_file = await toBase64(files.files[0]) 26 | 27 | axios 28 | .post( 29 | '/api/upload_music', 30 | { file: base64_file, filename: files.files[0].name }, 31 | {}, 32 | ) 33 | .then(res => { 34 | console.log(res.data) 35 | if ( 36 | res.data.result && 37 | res.data.result.created && 38 | res.data.result.created[0].dataTxId 39 | ) 40 | setMusicUrl( 41 | 'https://arweave.net/' + res.data.result.created[0].dataTxId, 42 | ) 43 | }) 44 | .catch(err => { 45 | console.log(err) 46 | }) 47 | } 48 | 49 | const createNewClicked = () => { 50 | newMusic() 51 | } 52 | 53 | return ( 54 |
55 |
Upload New Music
56 | 57 |
58 | 64 |
65 | 66 |
67 |
Title
68 |
69 | setTitle(e.target.value)} 74 | /> 75 |
76 |
77 |
78 |
Music Url
79 |
80 | setMusicUrl(e.target.value)} 85 | /> 86 |
87 |
88 |
89 | 95 | 101 |
102 |
103 | ) 104 | } 105 | 106 | export default UploadModal 107 | -------------------------------------------------------------------------------- /components/activity.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import friend from '../assets/friend.svg' 3 | import { activities } from '../data/activities' 4 | import ActivityCard from './activityCard' 5 | 6 | const styles = { 7 | activity: `bg-black w-96 h-screen p-5 py-10 text-white`, 8 | title: `flex items-center justify-between mb-10`, 9 | profileAvatarContainer: `w-14 h-14 rounded-full -ml-2 mr-3`, 10 | activityCard: `flex mb-6`, 11 | } 12 | 13 | const Activity = () => { 14 | return ( 15 |
16 |
17 | Friend Activity 18 | 19 |
20 | 21 |
22 | {activities.map((activity, index) => { 23 | return ( 24 | 30 | ) 31 | })} 32 |
33 |
34 | ) 35 | } 36 | 37 | export default Activity 38 | -------------------------------------------------------------------------------- /components/activityCard.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | 3 | const ActivityCard = ({ title, subTitle, avatar }) => { 4 | return ( 5 |
6 |
7 | 14 |
15 |
16 |
{title}
17 |
{subTitle}
18 |
19 |
20 | ) 21 | } 22 | 23 | export default ActivityCard 24 | 25 | const styles = { 26 | profileAvatarContainer: `w-14 h-14 rounded-full -ml-2 mr-3`, 27 | activityCard: `flex mb-6 cursor-pointer hover:opacity-50`, 28 | avatar: `rounded-full object-cover`, 29 | } 30 | -------------------------------------------------------------------------------- /components/header.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { SpotifyContext } from '../context/context' 3 | import Image from 'next/image' 4 | import UploadButton from './uploadButton' 5 | 6 | const style = { 7 | arrowButton: `bg-black mr-2 w-10 h-10 flex items-center justify-center rounded-full bg-opacity-50 cursor-pointer hover:bg-opacity-75`, 8 | headerRight: `flex`, 9 | profile: `flex items-center bg-black rounded-full p-1 px-3 bg-opacity-50 cursor-pointer hover:bg-opacity-75`, 10 | iconContainer: `ml-10`, 11 | title: `text-6xl font-extrabold`, 12 | header: `max-w-7xl m-auto p-3`, 13 | headerWrapper: `flex items-center justify-between`, 14 | playlistTextContent: `flex items-end mt-10`, 15 | profileAvatarContainer: `w-7 h-7 rounded-full -ml-2 mr-3`, 16 | controlsContainer: `flex items-center mt-10`, 17 | playButton: `bg-green-500 w-16 h-16 flex pl-2 items-center justify-center rounded-full cursor-pointer`, 18 | } 19 | 20 | const Header = ({ setShowUploadMusic }) => { 21 | const { currentSong } = useContext(SpotifyContext) 22 | 23 | return ( 24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 | 37 | 38 |
39 |
40 | 41 |
42 |

Your Name

43 |
44 |
45 |
46 | 47 |
48 | 54 | 55 |
56 |
ALBUM
57 |
Your Album
58 |
59 |
60 | 61 |
62 |

63 | SteamBeats • 2020 • 46 songs, 3 64 | hr 20 min 65 |

66 |
67 |
68 |
69 | 70 |
71 |
72 | 73 |
74 |
75 | 76 |
77 |
78 | 79 |
80 |
81 | 82 |
83 |
84 |
85 | ) 86 | } 87 | 88 | export default Header 89 | -------------------------------------------------------------------------------- /components/login.js: -------------------------------------------------------------------------------- 1 | import { useWallet } from '@solana/wallet-adapter-react' 2 | import { WalletMultiButton } from '@solana/wallet-adapter-react-ui' 3 | import { Payment } from './payment' 4 | 5 | const Login = () => { 6 | const wallet = useWallet() 7 | 8 | /** show payment UI if wallet is connected */ 9 | if (wallet.connected) return 10 | 11 | return ( 12 |
13 |

Login to access this app

14 | 15 |
16 | ) 17 | } 18 | 19 | export default Login 20 | 21 | const styles = { 22 | loginPage: `w-screen h-screen bg-white flex justify-center flex-col items-center`, 23 | text: `text-4xl text-black mb-10`, 24 | } 25 | -------------------------------------------------------------------------------- /components/nav.js: -------------------------------------------------------------------------------- 1 | import NavLink from './navLink' 2 | 3 | const styles = { 4 | nav: `bg-black h-screen w-96 p-5 py-10 `, 5 | link: `hover:text-[#fff]`, 6 | playlistName: `text-[#b3b3b3] cursor-default text-sm hover:text-[#fff]` 7 | } 8 | 9 | const Nav = () => { 10 | return ( 11 |
12 |
13 | 14 | 19 | 24 |
25 | 26 |
27 | 32 | 37 |
38 | 39 |
40 |

GHOST SONGS

41 |

42 | CarPlay Vol.2 43 |

44 |

45 | Country Dump 46 |

47 |

48 | Energy Booster: Country 49 |

50 |

51 | Funky 52 |

53 |

54 | Coaching 🔥 55 |

56 |

57 | Country 58 |

59 |

60 | Your Top Songs 2019 61 |

62 |
63 |
64 | ) 65 | } 66 | 67 | export default Nav 68 | -------------------------------------------------------------------------------- /components/navLink.js: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | 3 | const styles = { 4 | navLink: `flex item-center mb-5 cursor-pointer hover:text-[#fff] text-[#b3b3b3]`, 5 | navLinkText: `ml-5`, 6 | } 7 | 8 | const NavLink = ({ title, icon }) => { 9 | return ( 10 |
11 | 12 |

{title}

13 |
14 | ) 15 | } 16 | 17 | export default NavLink 18 | -------------------------------------------------------------------------------- /components/payment.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { getProgramInstance } from '../utils/utils' 3 | import { TOKEN_PROGRAM_ID } from '@solana/spl-token' 4 | import { SOLANA_HOST } from '../utils/const' 5 | import { PublicKey } from '@solana/web3.js' 6 | import { useWallet } from '@solana/wallet-adapter-react' 7 | import HomePage from '../pages/homepage' 8 | 9 | const anchor = require('@project-serum/anchor') 10 | 11 | const { web3 } = anchor 12 | const { SystemProgram } = web3 13 | const utf8 = anchor.utils.bytes.utf8 14 | 15 | const defaultAccounts = { 16 | tokenProgram: TOKEN_PROGRAM_ID, 17 | clock: anchor.web3.SYSVAR_CLOCK_PUBKEY, 18 | systemProgram: SystemProgram.programId, 19 | } 20 | 21 | export const Payment = () => { 22 | const wallet = useWallet() 23 | const connection = new anchor.web3.Connection(SOLANA_HOST) 24 | const program = getProgramInstance(connection, wallet) 25 | const [payers, setPayers] = useState([]) 26 | const [isPaid, setPaid] = useState(false) 27 | 28 | useEffect(() => { 29 | if (wallet.connected) getAllWallets() 30 | }, [wallet.connected, isPaid]) 31 | 32 | const getAllWallets = async () => { 33 | const payerList = await program.account.payerAccount.all() 34 | setPayers(payerList) 35 | 36 | payerList.forEach(payer => { 37 | if (payer.account.wallet.toBase58() == wallet.publicKey.toBase58()) 38 | setPaid(true) 39 | }) 40 | } 41 | 42 | const payClicked = async () => { 43 | let [payerSigner] = await anchor.web3.PublicKey.findProgramAddress( 44 | [utf8.encode('payer'), wallet.publicKey.toBuffer()], 45 | program.programId, 46 | ) 47 | 48 | let payerInfo 49 | 50 | try { 51 | payerInfo = await program.account.payerAccount.fetch(payerSigner) 52 | } catch (e) { 53 | try { 54 | await program.rpc.acceptPayment({ 55 | accounts: { 56 | payerWallet: payerSigner, 57 | receiver: new PublicKey( 58 | 'FdGbqLGZQgTpqXc1bKa41YsJTWHPxfwZKmTykjG6Jj1V', 59 | ), 60 | authority: wallet.publicKey, 61 | ...defaultAccounts, 62 | }, 63 | }) 64 | alert('Transaction proceed') 65 | } catch (e) { 66 | alert(e.message) 67 | } 68 | } 69 | } 70 | 71 | /** show homepage if user makes payment */ 72 | if (isPaid) return 73 | 74 | /** Payment Component */ 75 | return ( 76 |
77 |

Make payment

78 |
79 | 86 | 89 |
90 |
91 | ) 92 | } 93 | 94 | const styles = { 95 | main: `w-screen h-screen bg-white text-black flex flex-col justify-center items-center`, 96 | button: `bg-[#22C55E] m-3 text-white font-bold py-4 px-7 rounded-full hover:opacity-70 transition`, 97 | text: `text-4xl text-black mb-10`, 98 | buttons: `flex items-center`, 99 | } 100 | -------------------------------------------------------------------------------- /components/playerControls.js: -------------------------------------------------------------------------------- 1 | import { useContext } from "react" 2 | import { SpotifyContext } from "../context/context" 3 | import Image from "next/image" 4 | import next from "../assets/next.svg" 5 | import previous from "../assets/previous.svg" 6 | import speaker from "../assets/speaker.svg" 7 | import repeat from "../assets/repeat.svg" 8 | import shuffle from "../assets/shuffle.svg" 9 | import playRounded from "../assets/playRounded.svg" 10 | import pauseIcon from "../assets/pause.svg" 11 | 12 | const PlayerControls = ({songs}) => { 13 | const { currentSong, isPlaying, volume, onVolumeChange, timestamp, progress, playNext, playPrevious, isPaused, play, pause, onProgressChange } = useContext(SpotifyContext) 14 | 15 | if (!isPlaying) return null 16 | 17 | /** Show component if song is playing */ 18 | return ( 19 |
20 |
21 |
22 | 30 |
31 |
32 |

{currentSong.title}

33 |

{'artist'}

34 | {/*

{currentSong.artiste}

*/} 35 |
36 |
37 |
38 |
39 |
40 | 41 |
42 |
playPrevious(songs)} className={styles.controlIcon}> 43 | 44 |
45 | 46 | {isPaused ?
47 | :
} 48 | 49 |
playNext(songs)} className={styles.controlIcon}> 50 | 51 |
52 |
53 | 54 |
55 |
56 |
57 | {timestamp} 58 | onProgressChange(e)} 61 | type='range' 62 | className={styles.range} 63 | /> 64 | {'2:43'} 65 | {/* {currentSong.songLength} */} 66 |
67 |
68 | 69 |
70 |
71 | 72 | onVolumeChange(e)} type='range' id='volume-range' /> 73 |
74 |
75 |
76 | ) 77 | } 78 | 79 | export default PlayerControls 80 | 81 | const styles = { 82 | albumCoverContainer: `w-20 h-20 mr-3`, 83 | coverPhoto: `object-cover`, 84 | mainControl: `fixed bottom-0 left-0 p-5 py-3 pr-10 w-screen bg-[#242424] z-40 flex items-center justify-between`, 85 | // range: `appearance-none mx-3 hover:bg-[#000] h-1 hover:bg-[#22C55E] bg-[#fff] w-[500px]`, 86 | // volumeRange: `mx-3 -hue-rotate-90 h-1`, 87 | flexCenter: `flex items-center`, 88 | controlIcon: `mr-5 cursor-pointer hover:opacity-100 opacity-50`, 89 | playIcon: `mr-5 w-10 h-10 cursor-pointer hover:opacity-50`, 90 | pauseIconStyle: `mt-3 w-10 h-10 cursor-pointer hover:opacity-50`, 91 | controlIconsContainer: `flex items-center justify-center mb-2`, 92 | } -------------------------------------------------------------------------------- /components/playlist.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import TableRow from './table/tableRow' 3 | 4 | const Playlist = ({songs}) => { 5 | return ( 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | {songs.map(song => { 21 | return 22 | })} 23 |
#TITLEPLAYS 14 | 15 |
24 |
25 | ) 26 | } 27 | 28 | export default Playlist 29 | 30 | const styles = { 31 | table: `w-full text-left`, 32 | tableWrapper: `max-w-7xl m-auto p-3 mt-5 mb-40`, 33 | tableHeader: `border-b border-gray-100/20 pb-5 opacity-50`, 34 | } 35 | -------------------------------------------------------------------------------- /components/table/tableRow.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { SpotifyContext } from '../../context/context' 3 | 4 | const TableRow = ({ props }) => { 5 | const { playOnSelect } = useContext(SpotifyContext) 6 | 7 | return ( 8 | 9 | playOnSelect(props)}> 10 | {props.index} 11 | 12 |
13 |

{props.title}

14 |

{'artist'}

15 | {/*

{props.artiste}

*/} 16 |
17 | 18 | {'10,000'} 19 | {/* {props.plays} */} 20 | {'2:43'} 21 | {/*{props.songLength} */} 22 | 23 | 24 | ) 25 | } 26 | 27 | export default TableRow 28 | 29 | const styles = { 30 | th: `pb-5 hover:opacity-50 cursor-pointer`, 31 | } 32 | -------------------------------------------------------------------------------- /components/uploadButton.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { SpotifyContext } from '../context/context' 3 | 4 | const styles = { 5 | uploadButton: `bg-green-500 mr-10 px-3 py-1.5 cursor-pointer hover:scale-95 transition rounded-full`, 6 | } 7 | 8 | const UploadButton = ({ setShowUploadMusic }) => { 9 | // const { uploadMusic } = useContext(SpotifyContext) 10 | 11 | const uploadClicked = () => { 12 | console.log(setShowUploadMusic) 13 | setShowUploadMusic(true) 14 | } 15 | 16 | return ( 17 |
18 |
19 | Upload music 20 |
21 |
22 | ) 23 | } 24 | 25 | export default UploadButton 26 | -------------------------------------------------------------------------------- /context/WalletConnectionProvider.js: -------------------------------------------------------------------------------- 1 | import { WalletAdapterNetwork } from '@solana/wallet-adapter-base' 2 | import { 3 | ConnectionProvider, 4 | WalletProvider, 5 | } from '@solana/wallet-adapter-react' 6 | import { WalletModalProvider } from '@solana/wallet-adapter-react-ui' 7 | import { PhantomWalletAdapter } from '@solana/wallet-adapter-wallets' 8 | import { clusterApiUrl } from '@solana/web3.js' 9 | import { FC, useMemo } from 'react' 10 | import { CLUSTER, SOLANA_HOST } from '../utils/const' 11 | 12 | const WalletConnectionProvider = ({ children }) => { 13 | // const network = process.env.NEXT_PUBLIC_SOLANA_NETWORK as WalletAdapterNetwork; 14 | const endpoint = useMemo(() => SOLANA_HOST, []) 15 | 16 | const wallets = useMemo(() => [new PhantomWalletAdapter()], []) 17 | 18 | return ( 19 | 20 | 21 | {children} 22 | 23 | 24 | ) 25 | } 26 | 27 | export default WalletConnectionProvider 28 | -------------------------------------------------------------------------------- /context/context.js: -------------------------------------------------------------------------------- 1 | import { createContext, useState, useEffect } from "react" 2 | 3 | export const SpotifyContext = createContext() 4 | 5 | export const SpotifyProvider = ({ children }) => { 6 | const [currentSong, setCurrentSong] = useState({}) 7 | const [isPlaying, setIsPlaying] = useState(false) 8 | const [isPaused, setIsPaused] = useState(false) 9 | const [progress, setProgress] = useState(false) 10 | const [volume, setVolume] = useState(false) 11 | const [timestamp, setTimestamp] = useState() 12 | 13 | useEffect(() => { 14 | if (isPlaying) { 15 | let audio = document.querySelector('#audio-element') 16 | 17 | audio.addEventListener("timeupdate", function () { 18 | setTimestamp(secondsToMinSec(audio.currentTime)) 19 | }, false) 20 | } 21 | }, [isPlaying]) 22 | 23 | const pause = () => { 24 | setIsPaused(true) 25 | document.querySelector('#audio-element').pause() 26 | } 27 | 28 | const play = () => { 29 | document.querySelector('#audio-element').play() 30 | setIsPaused(false) 31 | } 32 | 33 | const playOnSelect = (song) => { 34 | try { 35 | document.querySelector('#audio-element').src = song.musicUrl 36 | document.querySelector('#audio-element').play() 37 | setCurrentSong(song) 38 | setIsPlaying(true) 39 | setIsPaused(false) 40 | } catch (e) { } 41 | } 42 | 43 | const secondsToMinSec = (value) => { 44 | const minute = Math.round(value / 60); 45 | let second = Math.round(value % 60); 46 | second = second >= 10 ? second : '0' + second; 47 | return minute + ':' + second; 48 | } 49 | 50 | const updateProgress = e => { 51 | const _progress = e.target.currentTime / e.target.duration 52 | setProgress(_progress.toFixed(2) * 100) 53 | } 54 | 55 | const updateVolume = (e) => { 56 | try { 57 | setVolume(e.target.value) 58 | document.querySelector('#audio-element').volume = e.target.value 59 | } catch (e) { } 60 | } 61 | 62 | const onProgressChange = (e) => { 63 | const _progress = e.target.value / 100 64 | document.querySelector('#audio-element').currentTime = _progress * document.querySelector('#audio-element').duration 65 | } 66 | 67 | const onVolumeChange = (e) => { 68 | const _volume = e.target.value / 100 69 | document.querySelector('#audio-element').volume = _volume 70 | 71 | } 72 | 73 | const playNext = (songs) => { 74 | const id = songs.findIndex(value => value.account === currentSong); 75 | if (songs.length === id + 1) { 76 | playOnSelect(songs[0].account) 77 | setCurrentSong(songs[0].account) 78 | return 79 | } 80 | const nextSong = songs[id + 1]; 81 | playOnSelect(nextSong.account); 82 | } 83 | 84 | const playPrevious = (songs) => { 85 | const id = songs.findIndex(value => value.account === currentSong); 86 | if (id === 0) { 87 | playOnSelect(songs[songs.length - 1].account) 88 | setCurrentSong(songs[songs.length - 1].account) 89 | return 90 | } 91 | const previousSong = songs[id - 1]; 92 | playOnSelect(previousSong.account); 93 | } 94 | return 109 | {children} 110 | 111 | } 112 | -------------------------------------------------------------------------------- /data/activities.js: -------------------------------------------------------------------------------- 1 | export const activities = [ 2 | { 3 | index: 1, 4 | title: 'Ambient Gold', 5 | subTitle: 'Lo-Fi', 6 | avatar: 7 | 'https://kajabi-storefronts-production.kajabi-cdn.com/kajabi-storefronts-production/themes/284832/settings_images/rLlCifhXRJiT0RoN2FjK_Logo_roundbackground_black.png', 8 | }, 9 | { 10 | index: 2, 11 | title: 'Kill it anyway', 12 | subTitle: 'SteamBeats originals', 13 | avatar: 14 | 'https://upload.wikimedia.org/wikipedia/commons/3/3b/Javascript_Logo.png', 15 | }, 16 | { 17 | index: 3, 18 | title: 'Gift of a Guest', 19 | subTitle: 'SteamBeats originals', 20 | avatar: 'https://i.ytimg.com/vi/Yaeu73hr_eM/maxresdefault.jpg', 21 | }, 22 | ] 23 | -------------------------------------------------------------------------------- /data/songs.js: -------------------------------------------------------------------------------- 1 | export const songs = [ 2 | { 3 | index: 0, 4 | title: 'Ambient Gold', 5 | artiste: 'Lo-Fi', 6 | plays: '20,382', 7 | songLength: '2:43', 8 | album: 'Songs & Beats', 9 | cover: 10 | 'https://kajabi-storefronts-production.kajabi-cdn.com/kajabi-storefronts-production/themes/284832/settings_images/rLlCifhXRJiT0RoN2FjK_Logo_roundbackground_black.png', 11 | arweaveLink: 12 | 'https://tnrapvewequ7ofz7idqmz6xlqmfkplivoyfosxagcpg23rxe.arweave.net/m2IH1JYkKfcXP0DgzPrrgwqnrRV2CulcBhPN_rc_bkY', 13 | }, 14 | { 15 | index: 1, 16 | title: 'Kill it anyway', 17 | artiste: 'SteamBeats originals', 18 | plays: '1,324', 19 | songLength: '4:50', 20 | album: 'The Random House', 21 | cover: 22 | 'https://upload.wikimedia.org/wikipedia/commons/3/3b/Javascript_Logo.png', 23 | arweaveLink: 24 | 'https://rnvytwtlgebo5e57kjl5ax3omeow423vmeul25dwmgueyhst.arweave.net/i2uJ2msxAu6Tv1JX0F9uYR1ua_-3VhKL10dmGoTB5TA', 25 | }, 26 | { 27 | index: 2, 28 | title: 'Gift of a Guest', 29 | artiste: 'SteamBeats originals', 30 | plays: '45,422', 31 | songLength: '1:33', 32 | album: 'A Regular Album', 33 | cover: 'https://i.ytimg.com/vi/Yaeu73hr_eM/maxresdefault.jpg', 34 | arweaveLink: 35 | 'https://dr5vuvoh5dm7pdnv5i4qo5ezmfmqevs4ytrx5c3wskdby4dy.arweave.net/HHta_Vcfo2feNt-eo5_B3SZYVkCVlzE436LdpKGHHB4', 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /hooks/useSpotify.js: -------------------------------------------------------------------------------- 1 | import { TOKEN_PROGRAM_ID } from '@solana/spl-token' 2 | import { useWallet } from '@solana/wallet-adapter-react' 3 | import { SOLANA_HOST } from '../utils/const' 4 | import { getProgramInstance } from '../utils/utils' 5 | const anchor = require('@project-serum/anchor') 6 | const utf8 = anchor.utils.bytes.utf8 7 | const { BN, web3 } = anchor 8 | const { SystemProgram } = web3 9 | 10 | const defaultAccounts = { 11 | tokenProgram: TOKEN_PROGRAM_ID, 12 | clock: anchor.web3.SYSVAR_CLOCK_PUBKEY, 13 | systemProgram: SystemProgram.programId, 14 | } 15 | 16 | const useSpotify = ( 17 | musicUrl, 18 | title, 19 | setTitle, 20 | setMusicUrl, 21 | setShowUploadMusic, 22 | ) => { 23 | const wallet = useWallet() 24 | const connection = new anchor.web3.Connection(SOLANA_HOST) 25 | const program = getProgramInstance(connection, wallet) 26 | 27 | const getSongs = async () => { 28 | console.log('fetching') 29 | 30 | const songs = await program.account.musicAccount.all() 31 | console.log(songs) 32 | return songs; 33 | } 34 | 35 | const newMusic = async () => { 36 | const randomkey = anchor.web3.Keypair.generate().publicKey; 37 | 38 | let [music_pda] = await anchor.web3.PublicKey.findProgramAddress( 39 | [utf8.encode('music'), randomkey.toBuffer()], 40 | program.programId, 41 | ) 42 | 43 | const tx = await program.rpc.createMusic( 44 | title, 45 | musicUrl, 46 | { 47 | accounts: { 48 | music: music_pda, 49 | randomkey: randomkey, 50 | authority: wallet.publicKey, 51 | ...defaultAccounts, 52 | }, 53 | }, 54 | ) 55 | 56 | console.log(tx) 57 | 58 | setTitle('') 59 | setMusicUrl('') 60 | setShowUploadMusic(false) 61 | } 62 | 63 | return { newMusic, getSongs } 64 | } 65 | 66 | export default useSpotify 67 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | env: { 5 | REACT_APP_CLUSTER: process.env.REACT_APP_CLUSTER, 6 | }, 7 | images: { 8 | domains: [ 9 | 'kajabi-storefronts-production.kajabi-cdn.com', 10 | 'upload.wikimedia.org', 11 | 'i.ytimg.com', 12 | 'angartwork.akamaized.net' 13 | ], 14 | }, 15 | } 16 | 17 | module.exports = nextConfig 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bts-spotify-clone-blockchain", 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 | "@project-serum/anchor": "^0.22.1", 13 | "@project-serum/common": "^0.0.1-beta.3", 14 | "@project-serum/serum": "^0.13.61", 15 | "@solana/spl-token": "^0.2.0", 16 | "@solana/wallet-adapter-base": "^0.9.5", 17 | "@solana/wallet-adapter-react": "^0.15.4", 18 | "@solana/wallet-adapter-react-ui": "^0.9.6", 19 | "@solana/wallet-adapter-wallets": "^0.15.5", 20 | "@solana/web3.js": "^1.37.0", 21 | "ardrive-core-js": "^1.13.0", 22 | "autoprefixer": "^10.4.4", 23 | "axios": "^0.27.2", 24 | "dotenv": "^16.0.0", 25 | "multiparty": "^4.2.3", 26 | "next": "12.1.0", 27 | "next-connect": "^0.12.2", 28 | "parse-multipart-data": "^1.3.0", 29 | "postcss": "^8.4.12", 30 | "react": "17.0.2", 31 | "react-dom": "17.0.2", 32 | "swr": "^1.3.0", 33 | "tailwindcss": "^3.0.24" 34 | }, 35 | "devDependencies": { 36 | "autoprefixer": "^10.4.4", 37 | "eslint": "8.13.0", 38 | "eslint-config-next": "12.1.5", 39 | "postcss": "^8.4.12", 40 | "tailwindcss": "^3.0.24" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic' 2 | import '../styles/globals.css' 3 | import { SpotifyProvider } from '../context/context' 4 | require('@solana/wallet-adapter-react-ui/styles.css') 5 | 6 | const WalletConnectionProvider = dynamic( 7 | () => import('../context/WalletConnectionProvider'), 8 | { 9 | ssr: false, 10 | }, 11 | ) 12 | 13 | function MyApp({ Component, pageProps }) { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | export default MyApp 24 | -------------------------------------------------------------------------------- /pages/api/upload_music.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduc 2 | import { 3 | readJWKFile, 4 | arDriveFactory, 5 | wrapFileOrFolder, 6 | EID, 7 | } from 'ardrive-core-js' 8 | const fs = require('fs') 9 | const multipart = require('parse-multipart-data') 10 | 11 | function getMatching(string, regex) { 12 | // Helper function when using non-matching groups 13 | const matches = string.match(regex) 14 | if (!matches || matches.length < 2) { 15 | return null 16 | } 17 | return matches[1] 18 | } 19 | 20 | function dataURLtoFile(dataurl, filename) { 21 | var arr = dataurl.split(','), 22 | mime = arr[0].match(/:(.*?);/)[1], 23 | bstr = atob(arr[1]), 24 | n = bstr.length, 25 | u8arr = new Uint8Array(n) 26 | 27 | while (n--) { 28 | u8arr[n] = bstr.charCodeAt(n) 29 | } 30 | 31 | fs.writeFileSync(filename, Buffer.from(u8arr)) 32 | } 33 | export default function handler(req, res) { 34 | dataURLtoFile(req.body.file, req.body.filename) 35 | 36 | // Read wallet from file 37 | const myWallet = readJWKFile('./wallet.json') 38 | 39 | // Construct ArDrive class 40 | const arDrive = arDriveFactory({ wallet: myWallet }) 41 | 42 | const wrappedEntity = wrapFileOrFolder(`./${req.body.filename}`) 43 | const destFolderId = EID('77fac565-a8c0-48e0-b64f-e1c0604372d3') 44 | 45 | // Upload a public file to destination folder 46 | arDrive 47 | .uploadAllEntities({ 48 | entitiesToUpload: [{ wrappedEntity, destFolderId }], 49 | }) 50 | .then(result => res.status(200).json({ result: result })) 51 | .catch(error => res.status(200).json({ error: error })) 52 | } 53 | 54 | export const config = { 55 | api: { 56 | bodyParser: { 57 | sizeLimit: '50mb', // Set desired value here 58 | }, 59 | }, 60 | } 61 | -------------------------------------------------------------------------------- /pages/homepage.js: -------------------------------------------------------------------------------- 1 | import Header from '../components/header' 2 | import Nav from '../components/nav' 3 | import Playlist from '../components/playlist' 4 | import PlayerControls from '../components/playerControls' 5 | import Activity from '../components/activity' 6 | import { useState, useEffect } from 'react' 7 | import UploadModal from '../components/UploadModal' 8 | import useSpotify from '../hooks/useSpotify' 9 | 10 | const HomePage = () => { 11 | const [showUploadMusic, setShowUploadMusic] = useState(false) 12 | const [title, setTitle] = useState('') 13 | const [musicUrl, setMusicUrl] = useState('') 14 | 15 | const { newMusic, getSongs } = useSpotify( 16 | musicUrl, 17 | title, 18 | setTitle, 19 | setMusicUrl, 20 | setShowUploadMusic, 21 | ) 22 | 23 | const [songs, setSongs] = useState([]) 24 | 25 | useEffect(() => { 26 | getSongs().then(songs => { 27 | setSongs(songs) 28 | }) 29 | }, []) 30 | 31 | return ( 32 |
33 |
51 | ) 52 | } 53 | 54 | export default HomePage 55 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { SpotifyContext } from '../context/context' 3 | import Login from '../components/login' 4 | 5 | export default function Home() { 6 | const { updateProgress, updateVolume } = useContext(SpotifyContext) 7 | return ( 8 |
9 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/assets/add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/album.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverProgrammers/spotify-clone-blockchain/afe6e4409227b097d10c65323b8e87b2b7eece4b/public/assets/album.jpg -------------------------------------------------------------------------------- /public/assets/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverProgrammers/spotify-clone-blockchain/afe6e4409227b097d10c65323b8e87b2b7eece4b/public/assets/avatar.jpg -------------------------------------------------------------------------------- /public/assets/chevronLeft.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/chevronRight.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/disc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/friend.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/more.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/playRounded.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/playlist.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/previous.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/repeat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/shuffle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/speaker.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/time.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CleverProgrammers/spotify-clone-blockchain/afe6e4409227b097d10c65323b8e87b2b7eece4b/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 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 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [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.js`. 18 | 19 | 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. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | 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. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 2rem; 3 | } 4 | 5 | .main { 6 | min-height: 100vh; 7 | padding: 4rem 0; 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | 15 | .footer { 16 | display: flex; 17 | flex: 1; 18 | padding: 2rem 0; 19 | border-top: 1px solid #eaeaea; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .footer a { 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | flex-grow: 1; 29 | } 30 | 31 | .title a { 32 | color: #0070f3; 33 | text-decoration: none; 34 | } 35 | 36 | .title a:hover, 37 | .title a:focus, 38 | .title a:active { 39 | text-decoration: underline; 40 | } 41 | 42 | .title { 43 | margin: 0; 44 | line-height: 1.15; 45 | font-size: 4rem; 46 | } 47 | 48 | .title, 49 | .description { 50 | text-align: center; 51 | } 52 | 53 | .description { 54 | margin: 4rem 0; 55 | line-height: 1.5; 56 | font-size: 1.5rem; 57 | } 58 | 59 | .code { 60 | background: #fafafa; 61 | border-radius: 5px; 62 | padding: 0.75rem; 63 | font-size: 1.1rem; 64 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 65 | Bitstream Vera Sans Mono, Courier New, monospace; 66 | } 67 | 68 | .grid { 69 | display: flex; 70 | align-items: center; 71 | justify-content: center; 72 | flex-wrap: wrap; 73 | max-width: 800px; 74 | } 75 | 76 | .card { 77 | margin: 1rem; 78 | padding: 1.5rem; 79 | text-align: left; 80 | color: inherit; 81 | text-decoration: none; 82 | border: 1px solid #eaeaea; 83 | border-radius: 10px; 84 | transition: color 0.15s ease, border-color 0.15s ease; 85 | max-width: 300px; 86 | } 87 | 88 | .card:hover, 89 | .card:focus, 90 | .card:active { 91 | color: #0070f3; 92 | border-color: #0070f3; 93 | } 94 | 95 | .card h2 { 96 | margin: 0 0 1rem 0; 97 | font-size: 1.5rem; 98 | } 99 | 100 | .card p { 101 | margin: 0; 102 | font-size: 1.25rem; 103 | line-height: 1.5; 104 | } 105 | 106 | .logo { 107 | height: 1em; 108 | margin-left: 0.5rem; 109 | } 110 | 111 | @media (max-width: 600px) { 112 | .grid { 113 | width: 100%; 114 | flex-direction: column; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /styles/UploadModal.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | position: absolute; 3 | left: 50%; 4 | top: 50%; 5 | transform: translate(-50%, -50%); 6 | z-index: 10; 7 | background-color: black; 8 | min-width: 20rem; 9 | min-height: 20rem; 10 | border-radius: 10px; 11 | padding: 1.2rem; 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: center; 15 | align-items: center; 16 | } 17 | 18 | .modalButtons { 19 | display: flex; 20 | justify-content: space-evenly; 21 | margin-top: 20px; 22 | padding: 10px; 23 | } 24 | 25 | .title { 26 | font-weight: bolder; 27 | font-size: 1.5rem; 28 | margin: 1.5rem 0 3rem 0; 29 | } 30 | 31 | .inputField { 32 | width: 100%; 33 | margin-bottom: 1rem; 34 | } 35 | 36 | .inputTitle { 37 | font-weight: 600; 38 | font-size: 1rem; 39 | margin-bottom: 0.5rem; 40 | } 41 | 42 | .inputContainer { 43 | display: flex; 44 | border: 1px #ecf1f4 solid; 45 | border-radius: 1rem; 46 | padding: 0.6rem 0.4rem; 47 | background-color: #fafcfc; 48 | color: #000; 49 | } 50 | 51 | .input { 52 | flex: 1; 53 | border: none; 54 | outline: none; 55 | background: none; 56 | } 57 | 58 | .button { 59 | color: white; 60 | padding: 5px; 61 | border-radius: 10px; 62 | border: none; 63 | padding: 0.4rem 1rem; 64 | border-radius: 9px; 65 | color: #fff; 66 | cursor: pointer; 67 | transition: 0.2s linear; 68 | width: 8rem; 69 | align-items: center; 70 | justify-content: center; 71 | display: flex; 72 | font-weight: 600; 73 | font-size: 0.9rem; 74 | margin: 0.5rem; 75 | } 76 | 77 | .createButton { 78 | background-color: #4e44ce; 79 | } 80 | 81 | .cancelButton { 82 | border: 2px #fe2d55 solid; 83 | color: #fe2d55; 84 | } 85 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | padding: 0; 8 | margin: 0; 9 | min-height: 100vh; 10 | color: #fff; 11 | background: linear-gradient(to bottom, #BCA0A0, #242424b0, #242424); 12 | } 13 | 14 | a { 15 | color: inherit; 16 | text-decoration: none; 17 | } 18 | 19 | * { 20 | box-sizing: border-box; 21 | } 22 | 23 | input[type=range] { 24 | margin: 0 10px; 25 | height: 6px; 26 | border-radius: 100px; 27 | background: #fff; 28 | appearance: none; 29 | width: 560px; 30 | outline: none; 31 | } 32 | 33 | #volume-range { 34 | width: 200px; 35 | } 36 | 37 | input[type=range]::-moz-range-thumb { 38 | background: #22C55E; 39 | border: none; 40 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./pages/**/*.{js,ts,jsx,tsx}", 4 | "./components/**/*.{js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | -------------------------------------------------------------------------------- /utils/const.js: -------------------------------------------------------------------------------- 1 | import { clusterApiUrl, PublicKey } from '@solana/web3.js' 2 | import spotify from './spotify.json' 3 | 4 | export const SOLANA_HOST = clusterApiUrl('devnet') 5 | 6 | export const STABLE_POOL_PROGRAM_ID = new PublicKey( 7 | '5wSMdEYxSW7iB3rdE7c8yB3bqBUrVtvggjDdY5viyDLk', 8 | ) 9 | 10 | export const STABLE_POOL_IDL = spotify 11 | -------------------------------------------------------------------------------- /utils/spotify.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "name": "spotify_spl_program", 4 | "instructions": [ 5 | { 6 | "name": "acceptPayment", 7 | "accounts": [ 8 | { 9 | "name": "payerWallet", 10 | "isMut": true, 11 | "isSigner": false 12 | }, 13 | { 14 | "name": "receiver", 15 | "isMut": true, 16 | "isSigner": false 17 | }, 18 | { 19 | "name": "authority", 20 | "isMut": true, 21 | "isSigner": true 22 | }, 23 | { 24 | "name": "systemProgram", 25 | "isMut": false, 26 | "isSigner": false 27 | }, 28 | { 29 | "name": "tokenProgram", 30 | "isMut": false, 31 | "isSigner": false 32 | }, 33 | { 34 | "name": "clock", 35 | "isMut": false, 36 | "isSigner": false 37 | } 38 | ], 39 | "args": [] 40 | }, 41 | { 42 | "name": "createMusic", 43 | "accounts": [ 44 | { 45 | "name": "music", 46 | "isMut": true, 47 | "isSigner": false 48 | }, 49 | { 50 | "name": "randomkey", 51 | "isMut": true, 52 | "isSigner": false 53 | }, 54 | { 55 | "name": "authority", 56 | "isMut": true, 57 | "isSigner": true 58 | }, 59 | { 60 | "name": "systemProgram", 61 | "isMut": false, 62 | "isSigner": false 63 | }, 64 | { 65 | "name": "tokenProgram", 66 | "isMut": false, 67 | "isSigner": false 68 | }, 69 | { 70 | "name": "clock", 71 | "isMut": false, 72 | "isSigner": false 73 | } 74 | ], 75 | "args": [ 76 | { 77 | "name": "title", 78 | "type": "string" 79 | }, 80 | { 81 | "name": "musicUrl", 82 | "type": "string" 83 | } 84 | ] 85 | } 86 | ], 87 | "accounts": [ 88 | { 89 | "name": "PayerAccount", 90 | "type": { 91 | "kind": "struct", 92 | "fields": [ 93 | { 94 | "name": "wallet", 95 | "type": "publicKey" 96 | } 97 | ] 98 | } 99 | }, 100 | { 101 | "name": "MusicAccount", 102 | "type": { 103 | "kind": "struct", 104 | "fields": [ 105 | { 106 | "name": "authority", 107 | "type": "publicKey" 108 | }, 109 | { 110 | "name": "title", 111 | "type": "string" 112 | }, 113 | { 114 | "name": "musicUrl", 115 | "type": "string" 116 | } 117 | ] 118 | } 119 | } 120 | ], 121 | "metadata": { 122 | "address": "5wSMdEYxSW7iB3rdE7c8yB3bqBUrVtvggjDdY5viyDLk" 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /utils/utils.js: -------------------------------------------------------------------------------- 1 | import * as anchor from '@project-serum/anchor' 2 | import { Connection } from '@solana/web3.js' 3 | import { WalletNotConnectedError } from '@solana/wallet-adapter-base' 4 | import { STABLE_POOL_IDL, STABLE_POOL_PROGRAM_ID } from './const' 5 | 6 | // This command makes an Lottery 7 | export function getProgramInstance(connection, wallet) { 8 | if (!wallet.publicKey) throw new WalletNotConnectedError() 9 | 10 | const provider = new anchor.Provider( 11 | connection, 12 | wallet, 13 | anchor.Provider.defaultOptions(), 14 | ) 15 | // Read the generated IDL. 16 | const idl = STABLE_POOL_IDL 17 | 18 | // Address of the deployed program. 19 | const programId = STABLE_POOL_PROGRAM_ID 20 | 21 | // Generate the program client from IDL. 22 | const program = new anchor.Program(idl, programId, provider) 23 | 24 | return program 25 | } 26 | -------------------------------------------------------------------------------- /wallet.json: -------------------------------------------------------------------------------- 1 | { 2 | "wallet": "insert your wallet config file here" 3 | } 4 | --------------------------------------------------------------------------------