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

Added Credits and Play duplicatie song on Approval #134

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions next-app/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ GOOGLE_CLIENT_SECRET=YOUR_GOOGLE_CLIENT_SECRET
NEXTAUTH_SECRET="YOUR_NEXTAUTH_SECRET"
NEXT_PUBLIC_SECRET="YOUR_NEXTAUTH_SECRET"
NEXT_PUBLIC_WSS_URL="ws://localhost:3000"
NEXT_PUBLIC_PUBLICKEY="YOUR_PUBLIC_KEY"
NEXT_PUBLIC_SOL_PER_PAYMENT="YOUR_SOL_PER_PAYMENT"

# Postgres DB
DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postgres
Expand Down
86 changes: 86 additions & 0 deletions next-app/app/api/remcreds/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { authOptions } from "@/lib/auth-options";
import prisma from "@/lib/db";
import { getServerSession } from "next-auth";
import { NextRequest, NextResponse } from "next/server";


export const POST = async (req:NextRequest)=>{

try{
const session = await getServerSession(authOptions);
if (!session?.user.id) {
return NextResponse.json(
{
message: "Unauthenticated",
},
{
status: 403,
},
);
}
const data = await req.json();
if(!data.spaceId || (typeof data.spaceId)!=="string"){
return NextResponse.json(
{
message: "Space Id is required",
},
{
status: 403,
},
);
}
// check if space exists
const space = await prisma.space.findUnique({
where: { id: data.spaceId },
});
if (!space) {
return NextResponse.json(
{
message: "Space not found",
},
{
status: 403,
},
);
}
// check if users creds are available
const remainingCreds = await prisma.remainingCreds.findFirst({
where: {
userId: session.user.id,
spaceId: data.spaceId,
},
select: { remainingCreds: true },
});

if(!remainingCreds){
return NextResponse.json(
{
message: "No Credits available",
ok:false,
}
)
}

return NextResponse.json(
{
message: "Credits available",
ok:true,
}
)
}
catch(error:any){
if (error.message === "Unauthenticated Request") {
return NextResponse.json(
{ success: false, message: "You must be logged in to create a space" },
{ status: 401 }
);
}


return NextResponse.json(
{ success: false, message: `An unexpected error occurred: ${error.message}` },
{ status: 500 }
);
}

}
1 change: 1 addition & 0 deletions next-app/app/api/user/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ export const GET = async (req: NextRequest) => {
});
};


// dont static render
export const dynamic = "force-dynamic";
2 changes: 1 addition & 1 deletion next-app/app/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ export default async function Home(){
if (!session?.user.id) {
return <h1>Please Log in....</h1>;
}
return <HomeView></HomeView>
return <HomeView></HomeView>

}
2 changes: 1 addition & 1 deletion next-app/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { authOptions } from "@/lib/auth-options";

export default async function LandingPage() {
const session = await getServerSession(authOptions);
if (session?.user.id) redirect("/home");


return (
<div className="flex min-h-screen flex-col bg-gradient-to-br from-gray-900 via-purple-900 to-gray-900">
Expand Down
4 changes: 4 additions & 0 deletions next-app/app/spaces/[spaceId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import LoadingScreen from "@/components/LoadingScreen";
import { useRouter } from "next/navigation";


// Default styles that can be overridden by your app
import '@solana/wallet-adapter-react-ui/styles.css';



export default function Component({params:{spaceId}}:{params:{spaceId:string}}) {

Expand Down
6 changes: 5 additions & 1 deletion next-app/components/Appbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { signIn, signOut, useSession } from "next-auth/react";
import { Button } from "@/components/ui/button";
import { useRouter } from "next/navigation";
import { ThemeSwitcher } from "./ThemeSwitcher";
import { WalletMultiButton } from "@solana/wallet-adapter-react-ui";
import Link from "next/link";

export function Appbar({ showThemeSwitch = true }) {

export function Appbar({ showThemeSwitch = true , isSpectator=false }) {
const session = useSession();
const router = useRouter();

Expand All @@ -20,6 +22,7 @@ export function Appbar({ showThemeSwitch = true }) {
Muzer
</div>
<div className="flex items-center gap-x-2">
{isSpectator && <WalletMultiButton/>}
{session.data?.user && (
<Button
className="bg-purple-600 text-white hover:bg-purple-700"
Expand Down Expand Up @@ -57,6 +60,7 @@ export function Appbar({ showThemeSwitch = true }) {
</Link>
</div>
)}

{showThemeSwitch && <ThemeSwitcher />}
</div>
</div>
Expand Down
143 changes: 138 additions & 5 deletions next-app/components/StreamView/AddSongForm.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { YT_REGEX } from "@/lib/utils";
import { useSocket } from "@/context/socket-context";
import React from "react";
import React, { useCallback, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent } from "@/components/ui/card";
import LiteYouTubeEmbed from "react-lite-youtube-embed";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction } from "@solana/web3.js";
import { useSession } from "next-auth/react";



type Props = {
inputLink: string;
Expand All @@ -14,7 +19,9 @@ type Props = {
setInputLink: (value: string) => void;
loading: boolean;
enqueueToast: (type: "error" | "success", message: string) => void;
spaceId:string
spaceId:string,
isSpectator:boolean,
checkIsDuplicate:(url:string)=>boolean
};

export default function AddSongForm({
Expand All @@ -24,9 +31,16 @@ export default function AddSongForm({
loading,
setLoading,
userId,
spaceId
spaceId,
isSpectator,
checkIsDuplicate
}: Props) {
const { sendMessage } = useSocket();
const wallet = useWallet();
const {connection} = useConnection();
const user = useSession().data?.user;
const [duplicateDiv, setDuplicateDiv] = useState(false);


const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
Expand All @@ -41,24 +55,131 @@ export default function AddSongForm({
} else {
enqueueToast("error", "Invalid please use specified formate");
}
setLoading(false);
setInputLink("");
};

const finallyPlaySong = async (songLink:string)=>{
if(!wallet.publicKey || !connection){
enqueueToast("error", "Please connect your wallet");
return;
}
// check if user creds for this space is pending or not
const response = await fetch(`/api/remcreds`,{
method:"POST",
body:JSON.stringify({
spaceId:spaceId,
})
});
const data = await response.json();

if(!data.ok){
const transaction = new Transaction();
transaction.add(
SystemProgram.transfer({
fromPubkey: wallet.publicKey,
toPubkey: new PublicKey(process.env.NEXT_PUBLIC_PUBLICKEY as string),
lamports: Number(process.env.NEXT_PUBLIC_SOL_PER_PAYMENT) * LAMPORTS_PER_SOL,
})
)

// sign Transaction steps
const blockHash = await connection.getLatestBlockhash();
transaction.feePayer = wallet.publicKey;
transaction.recentBlockhash = blockHash.blockhash;
//@ts-ignore
const signed = await wallet.signTransaction(transaction);


const signature = await connection.sendRawTransaction(signed.serialize());

enqueueToast("success", `Transaction signature: ${signature}`);
await connection.confirmTransaction({
blockhash: blockHash.blockhash,
lastValidBlockHeight: blockHash.lastValidBlockHeight,
signature
});
enqueueToast("success", `Payment successful`);
}

sendMessage("pay-and-play-next", {
spaceId,
userId: user?.id,
url:songLink
});
}

const handlePayAndPlay = useCallback(async (e: React.FormEvent) => {
e.preventDefault();

if(!wallet.publicKey || !connection){
enqueueToast("error", "Please connect your wallet");
return;
}
if (!inputLink.match(YT_REGEX)) {
enqueueToast("error", "Invalid please use specified formate");
}
try{
setLoading(true);

//check if it is duplicate or not if it is accepted by user then play
if(checkIsDuplicate(inputLink)){

if(!duplicateDiv){
setDuplicateDiv(true);
return;
}
if(duplicateDiv){

setDuplicateDiv(false);
await finallyPlaySong(inputLink);
setLoading(false);
}
return;
}
await finallyPlaySong(inputLink);

}
catch(error){
enqueueToast("error", `Payment unsuccessful`);
}
setLoading(false);

},[ duplicateDiv,wallet , connection, inputLink]);

const videoId = inputLink ? inputLink.match(YT_REGEX)?.[1] : undefined;

return (
<>
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold">Add a song</h1>
<h1 className="text-xl font-bold">Add a song</h1>
</div>

<form onSubmit={handleSubmit} className="space-y-2">
<form onSubmit={handleSubmit} className="space-y-2 relative">
<Input
type="text"
placeholder="Please paste your link"
value={inputLink}
onChange={(e) => setInputLink(e.target.value)}
/>
{ duplicateDiv &&
<div className="w-full z-10 absolute top-10 right-0 transition-all ease-in duration-500 bg-black rounded-md flex flex-col gap-3 px-4 py-2 justify-center items-center ">
<h1 className="font-semibold text-lg">Duplicate Song</h1>
<Button
className="w-full"
type="submit"
onClick={handlePayAndPlay}
>
Accept
</Button>
<Button
className="w-full bg-red-500 hover:bg-red-400"
onClick={()=>{setDuplicateDiv(false);setLoading(false)}}
>
Cancel
</Button>
</div>
}
<Button
disabled={loading}
onClick={handleSubmit}
Expand All @@ -67,6 +188,18 @@ export default function AddSongForm({
>
{loading ? "Loading..." : "Add to Queue"}
</Button>

{ isSpectator &&
<Button
disabled={loading}
onClick={handlePayAndPlay}
type="submit"
className="w-full"
>
{loading ? "Loading..." : "Pay and Play"}
</Button>
}

</form>

{videoId && !loading && (
Expand Down
10 changes: 7 additions & 3 deletions next-app/components/StreamView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export default function StreamView({
return json.activeStream.stream;
});
setSpaceName(json.spaceName)

} catch (error) {
enqueueToast("error", "Something went wrong");
}
Expand All @@ -114,7 +115,9 @@ export default function StreamView({
userId: user?.id,
});
};

const checkIsDuplicate = (url:string)=>{
return queue.some((video)=>video.url===url)
}
const enqueueToast = (type: "error" | "success", message: string) => {
toast[type](message, {
position: "top-right",
Expand All @@ -129,7 +132,7 @@ export default function StreamView({

return (
<div className="flex min-h-screen flex-col">
<Appbar />
<Appbar isSpectator={!playVideo}/>
<div className='mx-auto text-2xl bg-gradient-to-r rounded-lg from-indigo-600 to-violet-800 font-bold'>
{spaceName}
</div>
Expand All @@ -153,8 +156,9 @@ export default function StreamView({
setInputLink={setInputLink}
setLoading={setLoading}
spaceId={spaceId}
isSpectator={!playVideo}
checkIsDuplicate={checkIsDuplicate}
/>

<NowPlaying
currentVideo={currentVideo}
playNext={playNext}
Expand Down
Loading