Skip to content
Open
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
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
[submodule "mini-app"]
path = mini-app
url = https://sirgawain0x@github.com/creativeplatform/mini-app-tv.git
42 changes: 42 additions & 0 deletions app/api/coinbase/session-token/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// app/api/coinbase/session-token/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
const { address, assets } = await req.json();

if (!address) {
return NextResponse.json({ error: "Missing address" }, { status: 400 });
}

// Prepare the request body for Coinbase
const body = {
addresses: [
{
address,
blockchains: ["base"], // or more if needed
},
],
assets: assets || ["USDC"],
};

// Call Coinbase Session Token API
const coinbaseRes = await fetch(
"https://api.developer.coinbase.com/onramp/v1/token",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"CDP-API-KEY": process.env.COINBASE_CDP_API_KEY as string,
},
body: JSON.stringify(body),
}
);

if (!coinbaseRes.ok) {
const error = await coinbaseRes.text();
return NextResponse.json({ error }, { status: 500 });
}

const data = await coinbaseRes.json();
return NextResponse.json({ sessionToken: data.sessionToken });
}
6 changes: 3 additions & 3 deletions app/apolloWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { ApolloClient, InMemoryCache } from "@apollo/client-integration-nextjs";

// have a function to create a client for you
export function makeClient() {
if (!process.env.NEXT_PUBLIC_API_URL)
throw new Error("NEXT_PUBLIC_API_URL is not defined");
if (!process.env.NEXT_PUBLIC_SNAPSHOT_API_URL)
throw new Error("Snapshot URL is not defined");
Comment on lines +9 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new error message "Snapshot URL is not defined" is a bit generic. Could we make it more specific by mentioning the actual environment variable name that's missing, similar to the previous message? This can be helpful for debugging.

Suggested change
if (!process.env.NEXT_PUBLIC_SNAPSHOT_API_URL)
throw new Error("Snapshot URL is not defined");
if (!process.env.NEXT_PUBLIC_SNAPSHOT_API_URL)
throw new Error("NEXT_PUBLIC_SNAPSHOT_API_URL is not defined. Please check your environment configuration.");


const httpLink = new HttpLink({
// this needs to be an absolute url, as relative urls cannot be used in SSR
uri: `${process.env.NEXT_PUBLIC_API_URL}/graphql`,
uri: `${process.env.NEXT_PUBLIC_SNAPSHOT_API_URL}/graphql`,
// you can disable result caching here if you want to
// (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
fetchOptions: {
Expand Down
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const metadata: Metadata = {
card: "summary_large_image",
title: "Creative TV",
description: "The Way Content Should Be.",
images: ["https://tv.creativeplatform.xyz/logo-tv.gif"],
images: ["https://tv.creativeplatform.xyz/creative-banner.png"],
},
};

Expand Down
4 changes: 2 additions & 2 deletions components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import useModularAccount from "@/lib/hooks/accountkit/useModularAccount";
import { createPublicClient, http } from "viem";
import { alchemy, mainnet, base, optimism } from "@account-kit/infra";
import { ArrowBigDown, ArrowBigUp } from "lucide-react";
import WertButton from "./wallet/buy/fund-button";
import WertFundButton from "./wallet/buy/wert-fund-button";
import { TokenBalance } from "./wallet/balance/TokenBalance";
import type { Chain as ViemChain } from "viem/chains";
import { AccountDropdown } from "@/components/account-dropdown/AccountDropdown";
Expand Down Expand Up @@ -309,7 +309,7 @@ export default function Navbar() {
<div className="bg-white dark:bg-gray-800 rounded-lg p-4">
<h2 className="text-xl font-bold mb-4">Buy Crypto</h2>
<p className="mb-4">Purchase crypto directly to your wallet.</p>
<WertButton onClose={() => setIsDialogOpen(false)} />
<WertFundButton />
<div className="flex justify-end">
<Button onClick={() => setIsDialogOpen(false)}>Close</Button>
</div>
Expand Down
29 changes: 28 additions & 1 deletion components/Videos/Upload/Create-thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CreateThumbnailForm from "./CreateThumbnailForm";
import { toast } from "sonner";
import { NFTConfig } from "@/lib/types/video-asset";
import { Skeleton } from "@/components/ui/skeleton";
import { Progress } from "@/components/ui/progress";

type CreateThumbnailProps = {
livePeerAssetId: string | undefined;
Expand Down Expand Up @@ -100,6 +101,8 @@ export default function CreateThumbnail({
thumbnailUri: selectedThumbnail,
nftConfig: nftConfig,
});
} else {
toast.error("Please select a thumbnail before submitting.");
}
};

Expand All @@ -112,6 +115,27 @@ export default function CreateThumbnail({
<h3 className="text-lg">
Video Transcoding: {String(livepeerAssetData?.status?.phase)}
</h3>
{livepeerAssetData?.status?.phase !== "ready" &&
livepeerAssetData?.status?.phase !== "failed" &&
typeof livepeerAssetData?.status?.progress === "number" && (
<div className="mt-4">
<Progress
value={
livepeerAssetData.status.progress > 1
? livepeerAssetData.status.progress
: livepeerAssetData.status.progress * 100
}
/>
<div className="text-center text-sm mt-1 text-muted-foreground">
{Math.round(
livepeerAssetData.status.progress > 1
? livepeerAssetData.status.progress
: livepeerAssetData.status.progress * 100
)}
%
</div>
</div>
)}
</div>
{livepeerAssetData?.status?.phase !== "ready" && (
<div className="my-6">
Expand All @@ -134,7 +158,10 @@ export default function CreateThumbnail({
</div>
<CreateThumbnailForm
onSelectThumbnailImages={(thumbnailUri: string) => {
handleComplete(thumbnailUri);
setSelectedThumbnail(thumbnailUri);
}}
onNFTConfigChange={(config: NFTConfig) => {
setNFTConfig(config);
}}
/>
</div>
Expand Down
143 changes: 89 additions & 54 deletions components/Videos/Upload/CreateThumbnailForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,19 @@ interface FormValues {
selectedImage: string;
}

type CreateThumbnailFormProps = {
interface CreateThumbnailFormProps {
onSelectThumbnailImages: (imageUrl: string) => void;
};
onNFTConfigChange?: (nftConfig: {
isMintable: boolean;
maxSupply: number;
price: number;
royaltyPercentage: number;
}) => void;
}

const CreateThumbnailForm = ({
onSelectThumbnailImages,
onNFTConfigChange,
}: CreateThumbnailFormProps) => {
const {
handleSubmit,
Expand Down Expand Up @@ -112,9 +119,32 @@ const CreateThumbnailForm = ({
console.log("Updated imagesUrl state:", imagesUrl);
}, [imagesUrl]);

// Watch NFT config and notify parent on change
useEffect(() => {
if (!onNFTConfigChange) return;
const subscription = watch((value, { name }) => {
const config = value.nftConfig;
if (
(name === "nftConfig" || name?.startsWith("nftConfig.")) &&
config &&
typeof config.isMintable === "boolean" &&
typeof config.maxSupply === "number" &&
typeof config.price === "number" &&
typeof config.royaltyPercentage === "number"
)
onNFTConfigChange({
isMintable: config.isMintable,
maxSupply: config.maxSupply,
price: config.price,
royaltyPercentage: config.royaltyPercentage,
});
});
return () => subscription.unsubscribe();
}, [onNFTConfigChange, watch]);

const handleSelectionChange = (value: string) => {
setSelectedImage(value);
onSelectThumbnailImages(value);
onSelectThumbnailImages(value); // Only update selection, do not trigger navigation
};

const radioValue = watch("selectedImage") ?? "";
Expand Down Expand Up @@ -183,6 +213,62 @@ const CreateThumbnailForm = ({
/>
{errors.prompt && <p className="text-red-500">{errors.prompt.message}</p>}

{/* Move Generate button and image results here */}
<Button type="submit" disabled={isSubmitting}>
<SparklesIcon className="mr-1 h-4 w-4" />
{isSubmitting ? "Generating..." : "Generate"}
</Button>

{errors["root"] && (
<p className="text-red-500">
Sorry, we couldn&rsquo;t generate your image from text. Please try
again.
</p>
)}

{/* Render Skeletons while loading */}
{loading ? (
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
{Array.from({ length: 4 }).map((_, idx) => (
<Skeleton key={idx} className="rounded-md w-full h-[200px]" />
))}
</div>
) : (
<RadioGroup
value={radioValue}
onValueChange={(value) => {
setValue("selectedImage", value);
handleSelectionChange(value);
}}
className="mt-4 grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4"
>
{imagesUrl.length === 0 && (
<p className="col-span-full text-center text-gray-500">
No images generated yet.
</p>
)}
{imagesUrl.map((img, idx) => (
<div key={idx} className="flex flex-col items-center">
<RadioGroupItem
value={img.url}
id={`thumbnail_checkbox_${idx}`}
className="mb-2"
/>
<Label htmlFor={`thumbnail_checkbox_${idx}`}>
<Image
src={img.url}
alt={`Thumbnail ${idx + 1}`}
width={200}
height={200}
className="rounded-md border object-cover"
/>
</Label>
</div>
))}
</RadioGroup>
)}

{/* NFT Configuration section moved below */}
<div className="mt-8 space-y-4 border-t pt-4">
<h3 className="text-lg font-semibold">NFT Configuration</h3>

Expand Down Expand Up @@ -277,57 +363,6 @@ const CreateThumbnailForm = ({
</div>
)}
</div>

<Button type="submit" disabled={isSubmitting}>
<SparklesIcon className="mr-1 h-4 w-4" />
{isSubmitting ? "Generating..." : "Generate"}
</Button>

{errors["root"] && (
<p className="text-red-500">{errors["root"].message}</p>
)}

{/* Render Skeletons while loading */}
{loading ? (
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
{Array.from({ length: 4 }).map((_, idx) => (
<Skeleton key={idx} className="rounded-md w-full h-[200px]" />
))}
</div>
) : (
<RadioGroup
value={radioValue}
onValueChange={(value) => {
setValue("selectedImage", value);
handleSelectionChange(value);
}}
className="mt-4 grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4"
>
{imagesUrl.length === 0 && (
<p className="col-span-full text-center text-gray-500">
No images generated yet.
</p>
)}
{imagesUrl.map((img, idx) => (
<div key={idx} className="flex flex-col items-center">
<RadioGroupItem
value={img.url}
id={`thumbnail_checkbox_${idx}`}
className="mb-2"
/>
<Label htmlFor={`thumbnail_checkbox_${idx}`}>
<Image
src={img.url}
alt={`Thumbnail ${idx + 1}`}
width={200}
height={200}
className="rounded-md border object-cover"
/>
</Label>
</div>
))}
</RadioGroup>
)}
</form>
);
};
Expand Down
4 changes: 2 additions & 2 deletions components/Videos/Upload/CreateThumbnailWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
"use client";

import CreateThumbnail from './Create-thumbnail';
import CreateThumbnail from "./Create-thumbnail";

export default function CreateThumbnailWrapper(props: any) {
return <CreateThumbnail {...props} />;
Expand Down
Loading