diff --git a/lib/contracts/constants.ts b/lib/contracts/constants.ts index 3b147818..3aa322dc 100644 --- a/lib/contracts/constants.ts +++ b/lib/contracts/constants.ts @@ -20,7 +20,8 @@ export const BASE_CHAIN_ID = 8453; /** StoryFactory — storyline + plot management * Base Sepolia: 0x05C4d59529807316D6fA09cdaA509adDfe85b474 * Base Mainnet: TBD (replace after mainnet deployment) */ -export const STORY_FACTORY = "0x0000000000000000000000000000000000000000" as const; +export const STORY_FACTORY = (process.env.NEXT_PUBLIC_CONTRACT_ADDRESS ?? + "0x0000000000000000000000000000000000000000") as `0x${string}`; /** ZapPlotLinkMCV2 — one-click buy (ETH/USDC/HUNT -> storyline token) */ export const ZAP_PLOTLINK = "0x0000000000000000000000000000000000000000" as const; diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts new file mode 100644 index 00000000..edcaaad7 --- /dev/null +++ b/src/app/api/upload/route.ts @@ -0,0 +1,20 @@ +import { NextRequest, NextResponse } from "next/server"; +import { uploadWithRetry } from "../../../../lib/filebase"; + +export async function POST(req: NextRequest) { + try { + const { content, key } = await req.json(); + if (!content || !key) { + return NextResponse.json( + { error: "Missing content or key" }, + { status: 400 }, + ); + } + + const cid = await uploadWithRetry(content, key); + return NextResponse.json({ cid }); + } catch (err) { + const message = err instanceof Error ? err.message : "Upload failed"; + return NextResponse.json({ error: message }, { status: 500 }); + } +} diff --git a/src/app/create/page.tsx b/src/app/create/page.tsx new file mode 100644 index 00000000..67ae38a2 --- /dev/null +++ b/src/app/create/page.tsx @@ -0,0 +1,164 @@ +"use client"; + +import { useState } from "react"; +import { useAccount } from "wagmi"; +import { ConnectWallet } from "../../components/ConnectWallet"; +import { + validateContentLength, + MIN_CONTENT_LENGTH, + MAX_CONTENT_LENGTH, +} from "../../../lib/content"; +import { usePublishStoryline, type PublishState } from "../../hooks/usePublish"; +import Link from "next/link"; + +const STATE_LABELS: Record = { + idle: "", + uploading: "Uploading to IPFS...", + confirming: "Confirm in wallet...", + pending: "Publishing to Base...", + indexing: "Indexing...", + published: "Published!", + error: "Error", +}; + +export default function CreateStorylinePage() { + const { isConnected } = useAccount(); + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + const [hasDeadline, setHasDeadline] = useState(false); + + const { state, error, publish, reset } = usePublishStoryline(); + const { valid, charCount } = validateContentLength(content); + const titleValid = title.trim().length > 0; + const canSubmit = + state === "idle" || state === "error" + ? titleValid && valid + : false; + + if (!isConnected) { + return ( +
+

+ Connect your wallet to create a storyline. +

+ +
+ ); + } + + if (state === "published") { + return ( +
+

Storyline created!

+
+ + Discover + + +
+
+ ); + } + + const busy = state !== "idle" && state !== "error"; + + return ( +
+

+ Create Storyline +

+ +
{ + e.preventDefault(); + if (canSubmit) publish(title.trim(), content, hasDeadline); + }} + className="mt-8 space-y-6" + > + {/* Title */} +
+ + setTitle(e.target.value)} + disabled={busy} + placeholder="Enter storyline title" + className="border-border bg-surface text-foreground placeholder:text-muted w-full rounded border px-3 py-2 text-sm focus:border-accent focus:outline-none disabled:opacity-50" + /> +
+ + {/* Content */} +
+ +