diff --git a/apps/ai-hero/src/app/(content)/lists/[slug]/page.tsx b/apps/ai-hero/src/app/(content)/lists/[slug]/page.tsx new file mode 100644 index 00000000..1e57fa61 --- /dev/null +++ b/apps/ai-hero/src/app/(content)/lists/[slug]/page.tsx @@ -0,0 +1,157 @@ +import * as React from 'react' +import { Suspense } from 'react' +import { type Metadata, type ResolvingMetadata } from 'next' +import { cookies } from 'next/headers' +import Link from 'next/link' +import { notFound } from 'next/navigation' +import { Contributor } from '@/app/_components/contributor' +import { Share } from '@/components/share' +import type { List } from '@/lib/lists' +import { getAllLists, getList } from '@/lib/lists-query' +import { getOGImageUrlForResource } from '@/utils/get-og-image-url-for-resource' +import { recmaCodeHike, remarkCodeHike } from 'codehike/mdx' +import { ArrowRight } from 'lucide-react' +import { compileMDX } from 'next-mdx-remote/rsc' +import remarkGfm from 'remark-gfm' + +import { Button } from '@coursebuilder/ui' + +type Props = { + params: Promise<{ slug: string }> + searchParams: Promise<{ [key: string]: string | string[] | undefined }> +} + +export async function generateStaticParams() { + const lists = await getAllLists() + + return lists + .filter((list) => Boolean(list.fields?.slug)) + .map((list) => ({ + slug: list.fields?.slug, + })) +} + +export async function generateMetadata( + props: Props, + parent: ResolvingMetadata, +): Promise { + const params = await props.params + const list = await getList(params.slug) + + if (!list) { + return parent as Metadata + } + + return { + title: list.fields.title, + openGraph: { + images: [ + getOGImageUrlForResource({ + fields: { slug: list.fields.slug }, + id: list.id, + updatedAt: list.updatedAt, + }), + ], + }, + } +} + +export default async function ListPage(props: { + params: Promise<{ slug: string }> + searchParams: Promise<{ [key: string]: string | undefined }> +}) { + const searchParams = await props.searchParams + const params = await props.params + const list = await getList(params.slug).catch(() => notFound()) + + let body + + if (list.fields.body) { + const { content } = await compileMDX({ + source: list.fields.body, + // @ts-expect-error + components: { Code, Scrollycoding }, + options: { + mdxOptions: { + remarkPlugins: [ + remarkGfm, + [ + remarkCodeHike, + { + components: { code: 'Code' }, + }, + ], + ], + recmaPlugins: [ + [ + recmaCodeHike, + { + components: { code: 'Code' }, + }, + ], + ], + }, + }, + }) + body = content + } + + const firstResource = list.resources?.[0]?.resource + + return ( +
+
+
+
+

+ {list.fields.title} +

+ {list.fields.description && ( +
+

{list.fields.description}

+
+ )} + +
+ {firstResource && ( + + )} +
+
+
+
+
+ {body || 'No description found.'} +
+ +
+ +
+
+ ) +} diff --git a/packages/ui/resources-crud/edit-resources-action-bar.tsx b/packages/ui/resources-crud/edit-resources-action-bar.tsx index 022c43d6..55b9e32e 100644 --- a/packages/ui/resources-crud/edit-resources-action-bar.tsx +++ b/packages/ui/resources-crud/edit-resources-action-bar.tsx @@ -67,10 +67,10 @@ export function EditResourcesActionBar({ onPublish() }} type="button" - variant="default" + variant="outline" size="sm" disabled={isDisabled} - className="h-7 disabled:cursor-wait" + className="border-primary h-7 disabled:cursor-wait" > Save & Publish