diff --git a/apps/web/public/tool-art/audio-generation.svg b/apps/web/public/tool-art/audio-generation.svg new file mode 100644 index 000000000..d4895af46 --- /dev/null +++ b/apps/web/public/tool-art/audio-generation.svg @@ -0,0 +1 @@ +Audio Generation illustrationOriginal geometric Pencil and Pixel artwork for the Audio Generation tool.Audio Generation \ No newline at end of file diff --git a/apps/web/public/tool-art/brand-story.svg b/apps/web/public/tool-art/brand-story.svg new file mode 100644 index 000000000..391e9b2bf --- /dev/null +++ b/apps/web/public/tool-art/brand-story.svg @@ -0,0 +1 @@ +Brand & Story illustrationOriginal geometric Pencil and Pixel artwork for the Brand & Story tool.Brand & Story \ No newline at end of file diff --git a/apps/web/public/tool-art/character-chat.svg b/apps/web/public/tool-art/character-chat.svg new file mode 100644 index 000000000..74b25c36f --- /dev/null +++ b/apps/web/public/tool-art/character-chat.svg @@ -0,0 +1 @@ +Character Chat illustrationOriginal geometric Pencil and Pixel artwork for the Character Chat tool.Character Chat \ No newline at end of file diff --git a/apps/web/public/tool-art/creative-director.svg b/apps/web/public/tool-art/creative-director.svg new file mode 100644 index 000000000..79224f3c5 --- /dev/null +++ b/apps/web/public/tool-art/creative-director.svg @@ -0,0 +1 @@ +Creative Director illustrationOriginal geometric Pencil and Pixel artwork for the Creative Director tool.Creative Director \ No newline at end of file diff --git a/apps/web/public/tool-art/explorer-hero.svg b/apps/web/public/tool-art/explorer-hero.svg new file mode 100644 index 000000000..ca6f45b4c --- /dev/null +++ b/apps/web/public/tool-art/explorer-hero.svg @@ -0,0 +1 @@ +A connected creative toolboxAn original illustrated collage of image, video, audio and storytelling tools.VOICE & SOUND \ No newline at end of file diff --git a/apps/web/public/tool-art/image-studio.svg b/apps/web/public/tool-art/image-studio.svg new file mode 100644 index 000000000..a7c794fc3 --- /dev/null +++ b/apps/web/public/tool-art/image-studio.svg @@ -0,0 +1 @@ +Image Studio illustrationOriginal geometric Pencil and Pixel artwork for the Image Studio tool.Image Studio \ No newline at end of file diff --git a/apps/web/public/tool-art/mediakit.svg b/apps/web/public/tool-art/mediakit.svg new file mode 100644 index 000000000..f616b9d16 --- /dev/null +++ b/apps/web/public/tool-art/mediakit.svg @@ -0,0 +1 @@ +MediaKit Tools illustrationOriginal geometric Pencil and Pixel artwork for the MediaKit Tools tool.MediaKit Tools \ No newline at end of file diff --git a/apps/web/public/tool-art/precision-image.svg b/apps/web/public/tool-art/precision-image.svg new file mode 100644 index 000000000..ff6c535c3 --- /dev/null +++ b/apps/web/public/tool-art/precision-image.svg @@ -0,0 +1 @@ +Precision Image Studio illustrationOriginal geometric Pencil and Pixel artwork for the Precision Image Studio tool.Precision Image Studio \ No newline at end of file diff --git a/apps/web/public/tool-art/scriptwriter.svg b/apps/web/public/tool-art/scriptwriter.svg new file mode 100644 index 000000000..90c0b3d9f --- /dev/null +++ b/apps/web/public/tool-art/scriptwriter.svg @@ -0,0 +1 @@ +Scriptwriter illustrationOriginal geometric Pencil and Pixel artwork for the Scriptwriter tool.Scriptwriter \ No newline at end of file diff --git a/apps/web/public/tool-art/spokesperson.svg b/apps/web/public/tool-art/spokesperson.svg new file mode 100644 index 000000000..d8787d0a1 --- /dev/null +++ b/apps/web/public/tool-art/spokesperson.svg @@ -0,0 +1 @@ +AI Spokesperson illustrationOriginal geometric Pencil and Pixel artwork for the AI Spokesperson tool.AI Spokesperson \ No newline at end of file diff --git a/apps/web/public/tool-art/templates.svg b/apps/web/public/tool-art/templates.svg new file mode 100644 index 000000000..876dbaca2 --- /dev/null +++ b/apps/web/public/tool-art/templates.svg @@ -0,0 +1 @@ +Generation Templates illustrationOriginal geometric Pencil and Pixel artwork for the Generation Templates tool.Generation Templates \ No newline at end of file diff --git a/apps/web/public/tool-art/transcription.svg b/apps/web/public/tool-art/transcription.svg new file mode 100644 index 000000000..5dff75f47 --- /dev/null +++ b/apps/web/public/tool-art/transcription.svg @@ -0,0 +1 @@ +Speech & Transcription illustrationOriginal geometric Pencil and Pixel artwork for the Speech & Transcription tool.Speech & Transcription \ No newline at end of file diff --git a/apps/web/public/tool-art/video-editor.svg b/apps/web/public/tool-art/video-editor.svg new file mode 100644 index 000000000..4fc64071b --- /dev/null +++ b/apps/web/public/tool-art/video-editor.svg @@ -0,0 +1 @@ +Video Editing Desk illustrationOriginal geometric Pencil and Pixel artwork for the Video Editing Desk tool.Video Editing Desk \ No newline at end of file diff --git a/apps/web/public/tool-art/video-studio.svg b/apps/web/public/tool-art/video-studio.svg new file mode 100644 index 000000000..ffaf03a1a --- /dev/null +++ b/apps/web/public/tool-art/video-studio.svg @@ -0,0 +1 @@ +Video Studio illustrationOriginal geometric Pencil and Pixel artwork for the Video Studio tool.Video Studio \ No newline at end of file diff --git a/apps/web/public/tool-art/voice-casting.svg b/apps/web/public/tool-art/voice-casting.svg new file mode 100644 index 000000000..9d2384903 --- /dev/null +++ b/apps/web/public/tool-art/voice-casting.svg @@ -0,0 +1 @@ +Voice Casting Booth illustrationOriginal geometric Pencil and Pixel artwork for the Voice Casting Booth tool.Voice Casting Booth \ No newline at end of file diff --git a/apps/web/public/tool-art/voice-studio.svg b/apps/web/public/tool-art/voice-studio.svg new file mode 100644 index 000000000..7a8f570be --- /dev/null +++ b/apps/web/public/tool-art/voice-studio.svg @@ -0,0 +1 @@ +Voice Studio illustrationOriginal geometric Pencil and Pixel artwork for the Voice Studio tool.Voice Studio \ No newline at end of file diff --git a/apps/web/src/app/onboarding/page.tsx b/apps/web/src/app/onboarding/page.tsx index 2808381cf..2e4f781d9 100644 --- a/apps/web/src/app/onboarding/page.tsx +++ b/apps/web/src/app/onboarding/page.tsx @@ -17,9 +17,11 @@ export default async function OnboardingPage({ searchParams: Promise<{ returnTo?: string }>; }) { const requested = safeInvitationRoute((await searchParams).returnTo); - const learnReturn = requested.startsWith("/learn/start/") - ? requested - : undefined; + const contentReturn = + requested.startsWith("/learn/start/") || + requested.startsWith("/tools/start/") + ? requested + : undefined; const session = await requireRequestSession("/onboarding"); const membership = await db.membership.findFirst({ where: { @@ -31,7 +33,7 @@ export default async function OnboardingPage({ }); if (membership) { - redirect(learnReturn ?? `/app/${membership.organization.slug}`); + redirect(contentReturn ?? `/app/${membership.organization.slug}`); } return ( @@ -92,7 +94,7 @@ export default async function OnboardingPage({ - +
{( diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 10f7ba02c..8e8361f24 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -185,6 +185,7 @@ export default function HomePage() { aria-label="Public navigation" > {[ + ["/tools", "Explore Tools"], ["#platform", "Platform"], ["#models", "Models"], ["#workflow", "Workflow"], diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts index 0aba425d2..ec9d3d827 100644 --- a/apps/web/src/app/sitemap.ts +++ b/apps/web/src/app/sitemap.ts @@ -2,17 +2,30 @@ import type { MetadataRoute } from "next"; import { db } from "@aiwa/db"; import { publishedContent } from "@aiwa/learn"; import { learnOrigin } from "@/lib/learn/queries"; +import { publicTools } from "@/lib/public-tools"; +import { getPublicPricingCatalogue } from "@/lib/public-pricing"; export const dynamic = "force-dynamic"; export default async function sitemap(): Promise { const origin = learnOrigin(); - const posts = await db.learnPost.findMany({ - where: { publishedAt: { not: null } }, - take: 45000, - select: { slug: true, published: true, modifiedAt: true }, - }); + const [posts, pricing] = await Promise.all([ + db.learnPost.findMany({ + where: { publishedAt: { not: null } }, + take: 45000, + select: { slug: true, published: true, modifiedAt: true }, + }), + getPublicPricingCatalogue().catch(() => ({ models: [] })), + ]); + const operations = pricing.models.filter( + (item) => item.kind === "tool" && item.category === "tools", + ); return [ { url: origin }, { url: `${origin}/pricing` }, + { url: `${origin}/tools` }, + ...publicTools.map((tool) => ({ url: `${origin}/tools/${tool.slug}` })), + ...operations.map((item) => ({ + url: `${origin}/tools/operations/${encodeURIComponent(item.id)}`, + })), { url: `${origin}/learn` }, ...posts .filter((p) => !publishedContent(p.published).noindex) diff --git a/apps/web/src/app/tools/[slug]/page.tsx b/apps/web/src/app/tools/[slug]/page.tsx new file mode 100644 index 000000000..e34742d8d --- /dev/null +++ b/apps/web/src/app/tools/[slug]/page.tsx @@ -0,0 +1,342 @@ +import type { Metadata, Route } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { getPublicPricingCatalogue } from "@/lib/public-pricing"; +import { + publicToolById, + publicToolBySlug, + publicTools, + toolCategories, +} from "@/lib/public-tools"; +import { ToolCard } from "@/components/marketing/tools/tool-explorer"; +import { + ToolsConversion, + ToolsFooter, + ToolsHeader, +} from "@/components/marketing/tools/tools-shell"; +import { Icon } from "@/components/ui/icon"; + +export const dynamic = "force-dynamic"; +type PageProps = { params: Promise<{ slug: string }> }; +export function generateStaticParams() { + return publicTools.map((tool) => ({ slug: tool.slug })); +} +export async function generateMetadata({ + params, +}: PageProps): Promise { + const tool = publicToolBySlug((await params).slug); + if (!tool) return { title: "Tool not found", robots: { index: false } }; + return { + title: tool.title + " — " + tool.headline, + description: tool.summary.slice(0, 160), + alternates: { canonical: "/tools/" + tool.slug }, + robots: { index: true, follow: true }, + openGraph: { + title: tool.title + " · Aiwa Creators", + description: tool.summary, + type: "website", + images: [ + { + url: tool.cover, + width: 700, + height: 430, + alt: tool.title + " illustration", + }, + ], + }, + }; +} +export default async function ToolDetailPage({ params }: PageProps) { + const tool = publicToolBySlug((await params).slug); + if (!tool) notFound(); + const related = tool.relatedIds + .map(publicToolById) + .filter((item) => item !== undefined); + const group = toolCategories.find( + (category) => category.id === tool.category, + ); + const operationCatalogue = + tool.id === "mediakit" + ? await getPublicPricingCatalogue().catch(() => ({ models: [] })) + : null; + const operations = + operationCatalogue?.models.filter( + (item) => item.category === "tools" && item.kind === "tool", + ) ?? []; + const breadcrumbs = [ + { name: "Home", item: "/" }, + { name: "All tools", item: "/tools" }, + { name: tool.title, item: "/tools/" + tool.slug }, + ]; + const origin = process.env.APP_URL ?? "https://creator.aiwamediagroup.com"; + const breadcrumbData = { + "@context": "https://schema.org", + "@type": "BreadcrumbList", + itemListElement: breadcrumbs.map((crumb, index) => ({ + "@type": "ListItem", + position: index + 1, + name: crumb.name, + item: new URL(crumb.item, origin).toString(), + })), + }; + return ( +
+ +