From 14b247ffaaa0ae1b5d9d8908d8ba18820e13054e Mon Sep 17 00:00:00 2001 From: Riccardo Giorato Date: Fri, 26 Dec 2025 11:36:45 +0100 Subject: [PATCH] Add stories dashboard with API endpoint and navigation updates --- app/api/stories/route.ts | 70 ++++++++++++ app/stories/page.tsx | 164 +++++++++++++++++++++++++++ components/editor/editor-toolbar.tsx | 2 +- components/landing/create-button.tsx | 6 +- components/landing/navbar.tsx | 35 ++++-- 5 files changed, 265 insertions(+), 12 deletions(-) create mode 100644 app/api/stories/route.ts create mode 100644 app/stories/page.tsx diff --git a/app/api/stories/route.ts b/app/api/stories/route.ts new file mode 100644 index 0000000..1f25faf --- /dev/null +++ b/app/api/stories/route.ts @@ -0,0 +1,70 @@ +import { NextResponse } from "next/server"; +import { auth } from "@clerk/nextjs/server"; +import { db } from "@/lib/db"; +import { stories, pages } from "@/lib/schema"; +import { eq } from "drizzle-orm"; + +export async function GET() { + try { + const { userId } = await auth(); + + if (!userId) { + return NextResponse.json( + { error: "Authentication required" }, + { status: 401 } + ); + } + + // Get all stories for the user with their first page + const userStories = await db + .select({ + id: stories.id, + title: stories.title, + slug: stories.slug, + createdAt: stories.createdAt, + pageCount: pages.pageNumber, + coverImage: pages.generatedImageUrl, + }) + .from(stories) + .leftJoin(pages, eq(stories.id, pages.storyId)) + .where(eq(stories.userId, userId)) + .orderBy(stories.createdAt); + + // Group by story and find the max page number and first page image + const storyMap = new Map(); + + userStories.forEach((row) => { + const storyId = row.id; + if (!storyMap.has(storyId)) { + storyMap.set(storyId, { + id: row.id, + title: row.title, + slug: row.slug, + createdAt: row.createdAt, + pageCount: 0, + coverImage: null, + }); + } + + const story = storyMap.get(storyId); + if (row.pageCount && row.pageCount > story.pageCount) { + story.pageCount = row.pageCount; + } + if (row.pageCount === 1 && row.coverImage) { + story.coverImage = row.coverImage; + } + }); + + const storiesWithCovers = Array.from(storyMap.values()); + + return NextResponse.json({ + stories: storiesWithCovers + }); + } catch (error) { + console.error("Error fetching user stories:", error); + return NextResponse.json( + { error: "Failed to fetch stories" }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/stories/page.tsx b/app/stories/page.tsx new file mode 100644 index 0000000..afcffa5 --- /dev/null +++ b/app/stories/page.tsx @@ -0,0 +1,164 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { Plus, Loader2 } from "lucide-react"; +import { Navbar } from "@/components/landing/navbar"; + +interface Story { + id: string; + title: string; + slug: string; + createdAt: string; + pageCount: number; + coverImage: string | null; +} + +export default function StoriesPage() { + const router = useRouter(); + const [stories, setStories] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + fetchStories(); + }, []); + + const fetchStories = async () => { + try { + const response = await fetch("/api/stories"); + if (!response.ok) { + throw new Error("Failed to fetch stories"); + } + const data = await response.json(); + setStories(data.stories); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to load stories"); + } finally { + setIsLoading(false); + } + }; + + if (isLoading) { + return ( +
+
+
+
+
+ + + +
+
+ +

Loading your comic library...

+
+
+
+ ); + } + + if (error) { + return ( +
+
+
+
+
+ + + +
+
+

{error}

+ +
+
+
+ ); + } + + return ( +
+
+
+
+
+ + + +
+
+
+ {stories.length === 0 ? ( +
+
+ +
+

No comics yet

+

+ Create your first comic story to build your library! +

+
+ ) : ( +
+ {stories.map((story) => ( + + ))} +
+ )} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/components/editor/editor-toolbar.tsx b/components/editor/editor-toolbar.tsx index a130b45..832c64d 100644 --- a/components/editor/editor-toolbar.tsx +++ b/components/editor/editor-toolbar.tsx @@ -19,7 +19,7 @@ export function EditorToolbar({ title, onContinueStory, onInfoClick }: EditorToo + {isOnStoriesPage ? ( + + + + ) : ( + + + + )}