111 lines
3.2 KiB
TypeScript
111 lines
3.2 KiB
TypeScript
import type { Metadata } from 'next';
|
|
import { getLessonBySlug } from '@/lib/actions/lessonActions';
|
|
|
|
const BASE_URL = 'https://ayrisai.xyz';
|
|
|
|
interface Props {
|
|
params: Promise<{ locale: string; slug: string }>;
|
|
}
|
|
|
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
const { slug, locale } = await params;
|
|
const lesson = await getLessonBySlug(slug);
|
|
|
|
if (!lesson) {
|
|
return {
|
|
title: 'Lesson Not Found | ayris.tech',
|
|
};
|
|
}
|
|
|
|
const url = `${BASE_URL}/${locale}/lessons/${slug}`;
|
|
const description =
|
|
lesson.summary ||
|
|
`Watch the ${lesson.title} tutorial on ayris.tech YouTube. Download source code, read lesson notes, and grab the full project starter pack.`;
|
|
|
|
return {
|
|
title: lesson.title,
|
|
description,
|
|
keywords: [
|
|
...(lesson.tags || []),
|
|
'YouTube tutorial', 'source code download', 'ayris.tech', lesson.category,
|
|
],
|
|
alternates: { canonical: url },
|
|
openGraph: {
|
|
title: lesson.title,
|
|
description,
|
|
url,
|
|
type: 'video.other',
|
|
images: lesson.thumbnailUrl
|
|
? [{ url: lesson.thumbnailUrl, width: 1280, height: 720, alt: lesson.title }]
|
|
: [],
|
|
},
|
|
twitter: {
|
|
card: 'summary_large_image',
|
|
title: lesson.title,
|
|
description,
|
|
images: lesson.thumbnailUrl ? [lesson.thumbnailUrl] : [],
|
|
},
|
|
};
|
|
}
|
|
|
|
export default async function LessonDetailLayout({
|
|
children,
|
|
params,
|
|
}: {
|
|
children: React.ReactNode;
|
|
params: Promise<{ locale: string; slug: string }>;
|
|
}) {
|
|
const { slug, locale } = await params;
|
|
const lesson = await getLessonBySlug(slug);
|
|
|
|
const jsonLd = lesson
|
|
? {
|
|
'@context': 'https://schema.org',
|
|
'@graph': [
|
|
{
|
|
'@type': 'VideoObject',
|
|
name: lesson.title,
|
|
description: lesson.summary || lesson.title,
|
|
thumbnailUrl: lesson.thumbnailUrl || '',
|
|
uploadDate: lesson.createdAt || new Date().toISOString(),
|
|
duration: lesson.duration
|
|
? `PT${lesson.duration.replace(':', 'M')}S`
|
|
: undefined,
|
|
contentUrl: lesson.youtubeUrl || '',
|
|
embedUrl: lesson.youtubeId
|
|
? `https://www.youtube.com/embed/${lesson.youtubeId}`
|
|
: undefined,
|
|
author: { '@type': 'Person', name: 'ayris.tech', url: BASE_URL },
|
|
publisher: {
|
|
'@type': 'Organization',
|
|
name: 'ayris.tech',
|
|
url: BASE_URL,
|
|
logo: { '@type': 'ImageObject', url: `${BASE_URL}/logo.jpeg` },
|
|
},
|
|
},
|
|
{
|
|
'@type': 'BreadcrumbList',
|
|
itemListElement: [
|
|
{ '@type': 'ListItem', position: 1, name: 'Home', item: `${BASE_URL}/${locale}` },
|
|
{ '@type': 'ListItem', position: 2, name: 'Lessons', item: `${BASE_URL}/${locale}/lessons` },
|
|
{ '@type': 'ListItem', position: 3, name: lesson.title, item: `${BASE_URL}/${locale}/lessons/${slug}` },
|
|
],
|
|
},
|
|
],
|
|
}
|
|
: null;
|
|
|
|
return (
|
|
<>
|
|
{jsonLd && (
|
|
<script
|
|
type="application/ld+json"
|
|
suppressHydrationWarning
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
/>
|
|
)}
|
|
{children}
|
|
</>
|
|
);
|
|
}
|