Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate web3 payments #73

Closed
wants to merge 13 commits into from
91 changes: 90 additions & 1 deletion app/api/streams/next/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { RPC_URI } from "@/app/lib/constants";
import { prismaClient } from "@/app/lib/db";
import { Connection } from "@solana/web3.js";
import { getServerSession } from "next-auth";
import { NextResponse } from "next/server";
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";


const playNextSchema = z.object({
streamId: z.string().uuid(),
txnSig: z.string(),
})

export async function GET() {
const session = await getServerSession();
Expand Down Expand Up @@ -60,4 +69,84 @@ export async function GET() {
stream: mostUpvotedStream
})

}


export async function POST(req:NextRequest){

const session = await getServerSession();

const user = await prismaClient.user.findFirst({
where: {
email: session?.user?.email ?? ""
}
});

if (!user) {
return NextResponse.json({
message: "Unauthenticated"
}, {
status: 403
})
}

const data = await req.json();

const {streamId,txnSig} = playNextSchema.parse(data);

const connection = new Connection(RPC_URI);

const txResponse = await connection.getTransaction(txnSig,{ maxSupportedTransactionVersion: 0, commitment:"confirmed" });

if(!txResponse){
return NextResponse.json({
message:"Enter Valid Transaction Signature !"
},{
status:402,
})
}

const nextStream = await prismaClient.stream.findUnique({
where:{
id:streamId,
userId:user.id,
}
})

if (!nextStream) {
return NextResponse.json({
message: "Enter Valid Stream Id !"
}, {
status: 404,
})
}


await prismaClient.currentStream.delete({where:{userId:user.id}});

await Promise.all([prismaClient.currentStream.upsert({
where: {
streamId : nextStream.id,
userId:user.id
},
update: {
streamId : nextStream.id,
},
create: {
userId: user.id,
streamId : nextStream.id,
},
}),prismaClient.stream.update({
where: {
id : nextStream.id,
},
data: {
played: true,
playedTs: new Date()
}
})])

return NextResponse.json({
stream: nextStream
})
}
13 changes: 12 additions & 1 deletion app/components/Appbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,26 @@ import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
//@ts-ignore
import { Music } from "lucide-react"
import {
WalletDisconnectButton,
WalletMultiButton
} from '@solana/wallet-adapter-react-ui';
import { useWallet } from "@solana/wallet-adapter-react";

export function Appbar() {

const session = useSession();
const {wallet} = useWallet();

return <div className="flex justify-between px-20 pt-4">
<div className="text-lg font-bold flex flex-col justify-center text-white">
Muzer
</div>
<div>
<div className="flex items-center gap-x-3">
{
session.status === "authenticated" && (wallet ? <WalletDisconnectButton /> : <WalletMultiButton />)
}
{/* {wallet ? <WalletDisconnectButton /> : <WalletMultiButton />} */}
{session.data?.user && <Button className="bg-purple-600 text-white hover:bg-purple-700" onClick={() => signOut()}>Logout</Button>}
{!session.data?.user &&<Button className="bg-purple-600 text-white hover:bg-purple-700" onClick={() => signIn()}>Signin</Button>}
</div>
Expand Down
96 changes: 81 additions & 15 deletions app/components/StreamView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ import { Appbar } from './Appbar'
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
import 'react-lite-youtube-embed/dist/LiteYouTubeEmbed.css'
import { YT_REGEX } from '../lib/utils'
//@ts-ignore
import YouTubePlayer from 'youtube-player'
import { useSession } from "next-auth/react"
import type { Session } from "next-auth"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"
import { useConnection, useWallet } from '@solana/wallet-adapter-react'
import { PublicKey, SystemProgram, Transaction } from '@solana/web3.js';
import {DEFAULT_LAMPORT, PUBLIC_KEY_ADDRESS} from "@/app/lib/constants"

interface Video {
id: string
Expand Down Expand Up @@ -58,6 +62,10 @@ export default function StreamView({
const [isCreator, setIsCreator] = useState(false)
const [isEmptyQueueDialogOpen, setIsEmptyQueueDialogOpen] = useState(false)

const { publicKey, sendTransaction } = useWallet();
const { connection } = useConnection();


async function refreshStreams() {
try {
const res = await fetch(`/api/streams/?creatorId=${creatorId}`, {
Expand Down Expand Up @@ -173,23 +181,72 @@ export default function StreamView({
})
}

const playNext = async () => {
if (queue.length > 0) {
try {
setPlayNextLoader(true)
const data = await fetch('/api/streams/next', {
method: "GET",
const sendSOL = async() => {
if (!publicKey){
toast.error("Please connect the Wallet to continue");
return;
}

const lamports = DEFAULT_LAMPORT;

const {
context: { slot: minContextSlot },
value: { blockhash, lastValidBlockHeight }
} = await connection.getLatestBlockhashAndContext();

const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: new PublicKey(PUBLIC_KEY_ADDRESS),
lamports,
})
);

const signature = await sendTransaction(transaction, connection, { minContextSlot });
await connection.confirmTransaction({ blockhash, lastValidBlockHeight, signature });
return signature;
}

const playNext = async (streamId?:string) => {

if (queue.length > 0) {
try {
setPlayNextLoader(true)
let data;

if(streamId){

const txnSig = await sendSOL();

if(!txnSig){
toast.error("Can't get the Transaction details!!");
return;
}

data = await fetch('/api/streams/next', {
method: "POST",
body:JSON.stringify({
streamId,
txnSig,
})
})
const json = await data.json()
setCurrentVideo(json.stream)
setQueue(q => q.filter(x => x.id !== json.stream?.id))
} catch(e) {
console.error("Error playing next song:", e)
} finally {
setPlayNextLoader(false)
}
else{
data = await fetch('/api/streams/next', {
method: "GET",
})
}

const json = await data.json();
setCurrentVideo(json.stream)
setQueue(q => q.filter(x => x.id !== json.stream?.id))
} catch(e) {
console.error("Error playing next song:", e)
}finally {
setPlayNextLoader(false)
}
}
}

const handleShare = () => {
const shareableLink = `${window.location.origin}/creator/${creatorId}`
Expand Down Expand Up @@ -296,7 +353,16 @@ export default function StreamView({
<X className="h-4 w-4" />
</Button>
)}
</div>

<Button
variant="outline"
size="sm"
onClick={() => playNext(video.id)}
className="flex items-center space-x-1 bg-gray-800 text-white border-gray-700 hover:bg-gray-700"
>
Play Now
</Button>
</div>
</div>
</CardContent>
</Card>
Expand Down Expand Up @@ -349,7 +415,7 @@ export default function StreamView({
<p className="text-center py-8 text-gray-400">No video playing</p>
)}
{playVideo && (
<Button disabled={playNextLoader} onClick={playNext} className="w-full bg-purple-600 hover:bg-purple-700 text-white transition-colors">
<Button disabled={playNextLoader} onClick={()=>playNext()} className="w-full bg-purple-600 hover:bg-purple-700 text-white transition-colors">
<Play className="mr-2 h-4 w-4" /> {playNextLoader ? "Loading..." : "Play next"}
</Button>
)}
Expand Down
24 changes: 24 additions & 0 deletions app/components/WalletProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { FC, ReactNode } from 'react';
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
import {
WalletModalProvider,
} from '@solana/wallet-adapter-react-ui';

// Default styles that can be overridden by your app
import '@solana/wallet-adapter-react-ui/styles.css';
import { RPC_URI } from '../lib/constants';

export const Wallet: FC<{children:ReactNode}> = ({children}) => {

const endpoint = RPC_URI;

return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={[]} autoConnect>
<WalletModalProvider>
{children}
</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
);
};
3 changes: 3 additions & 0 deletions app/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const DEFAULT_LAMPORT = 1000_00_00; // EQUALS TO 0.01 SOL
export const PUBLIC_KEY_ADDRESS = process.env.NEXT_PUBLIC_ADDRESS || "";
export const RPC_URI = process.env.NEXT_PUBLIC_RPC_URI || "";
12 changes: 9 additions & 3 deletions app/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
"use client";

import { SessionProvider } from "next-auth/react";
import { Wallet } from "./components/WalletProvider";


export function Providers({ children }: {
children: React.ReactNode
}) {
return <SessionProvider>
{children}
</SessionProvider>
return (
<SessionProvider>
<Wallet>
{children}
</Wallet>
</SessionProvider>
)
}
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@
"@prisma/client": "5.19.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@solana/wallet-adapter-base": "^0.9.23",
"@solana/wallet-adapter-react": "^0.15.35",
"@solana/wallet-adapter-react-ui": "^0.9.35",
"@solana/wallet-adapter-wallets": "^0.19.32",
"@solana/web3.js": "^1.95.3",
"axios": "^1.7.5",
"bs58": "^4.0.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.436.0",
"next": "14.2.7",
"next-auth": "^4.24.7",
"pino-pretty": "^11.2.2",
"prisma": "^5.19.0",
"react": "^18",
"react-dom": "^18",
Expand Down
Loading
Loading