create-audora-next 0.1.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -5
- package/index.ts +9 -4
- package/package.json +6 -1
- package/templates/blog/README.md +164 -0
- package/templates/blog/bun.lock +1341 -0
- package/templates/blog/env.example.template +5 -0
- package/templates/blog/eslint.config.mjs +18 -0
- package/templates/blog/gitignore.template +41 -0
- package/templates/blog/husky.template/pre-commit +16 -0
- package/templates/blog/lint-staged.config.mjs +17 -0
- package/templates/blog/next.config.ts +38 -0
- package/templates/blog/package.json +59 -0
- package/templates/blog/postcss.config.mjs +7 -0
- package/templates/blog/public/favicon/apple-touch-icon.png +0 -0
- package/templates/blog/public/favicon/favicon-96x96.png +0 -0
- package/templates/blog/public/favicon/favicon.ico +0 -0
- package/templates/blog/public/favicon/favicon.svg +1 -0
- package/templates/blog/public/favicon/site.webmanifest +21 -0
- package/templates/blog/public/favicon/web-app-manifest-192x192.png +0 -0
- package/templates/blog/public/favicon/web-app-manifest-512x512.png +0 -0
- package/templates/blog/public/images/screenshot-desktop-dark.webp +0 -0
- package/templates/blog/public/images/screenshot-desktop-light.webp +0 -0
- package/templates/blog/public/images/screenshot-mobile-dark.webp +0 -0
- package/templates/blog/public/images/screenshot-mobile-light.webp +0 -0
- package/templates/blog/src/app/blogs/[slug]/page.tsx +171 -0
- package/templates/blog/src/app/blogs/page.tsx +108 -0
- package/templates/blog/src/app/layout.tsx +60 -0
- package/templates/blog/src/app/llms-full.txt/route.ts +97 -0
- package/templates/blog/src/app/llms.txt/route.ts +40 -0
- package/templates/blog/src/app/manifest.ts +61 -0
- package/templates/blog/src/app/page.tsx +57 -0
- package/templates/blog/src/app/robots.ts +16 -0
- package/templates/blog/src/app/sitemap.ts +52 -0
- package/templates/blog/src/blogs/components/animated-blog-list.tsx +33 -0
- package/templates/blog/src/blogs/components/blog-post-card.tsx +46 -0
- package/templates/blog/src/blogs/components/blog-section.tsx +34 -0
- package/templates/blog/src/blogs/components/blog-table-of-contents.tsx +369 -0
- package/templates/blog/src/blogs/components/copy-button.tsx +46 -0
- package/templates/blog/src/blogs/components/mdx.tsx +225 -0
- package/templates/blog/src/blogs/content/cosketch/cosketch-canvas-engine.mdx +186 -0
- package/templates/blog/src/blogs/content/cosketch/cosketch-docker-architecture.mdx +175 -0
- package/templates/blog/src/blogs/content/cosketch/cosketch-eraser-and-selection.mdx +207 -0
- package/templates/blog/src/blogs/content/hello-world.mdx +66 -0
- package/templates/blog/src/blogs/data/mdx.ts +68 -0
- package/templates/blog/src/blogs/utils/extract-headings.ts +38 -0
- package/templates/blog/src/components/copyable-code.tsx +41 -0
- package/templates/blog/src/components/footer.tsx +25 -0
- package/templates/blog/src/components/header.tsx +27 -0
- package/templates/blog/src/components/icons.tsx +84 -0
- package/templates/blog/src/components/section-heading.tsx +11 -0
- package/templates/blog/src/components/theme-provider.tsx +11 -0
- package/templates/blog/src/components/theme-toggle.tsx +20 -0
- package/templates/blog/src/components/view-all-link.tsx +56 -0
- package/templates/blog/src/config/site.ts +19 -0
- package/templates/blog/src/data/llms.ts +112 -0
- package/templates/blog/src/data/site.ts +52 -0
- package/templates/blog/src/lib/seo.ts +190 -0
- package/templates/blog/src/lib/utils.ts +83 -0
- package/templates/blog/src/styles/globals.css +99 -0
- package/templates/blog/src/utils/cn.ts +7 -0
- package/templates/blog/tsconfig.json +34 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { SITE_CONFIG } from "@/config/site";
|
|
2
|
+
import LLMS_DATA from "@/data/llms";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-static";
|
|
5
|
+
|
|
6
|
+
export function GET() {
|
|
7
|
+
const content = `# ${SITE_CONFIG.name}
|
|
8
|
+
|
|
9
|
+
> ${SITE_CONFIG.tagline}
|
|
10
|
+
|
|
11
|
+
${SITE_CONFIG.description}
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
\`\`\`bash
|
|
16
|
+
${LLMS_DATA.quickStart}
|
|
17
|
+
cd my-app
|
|
18
|
+
bun install
|
|
19
|
+
bun dev
|
|
20
|
+
\`\`\`
|
|
21
|
+
|
|
22
|
+
## Resources
|
|
23
|
+
|
|
24
|
+
- Summary: ${SITE_CONFIG.url}/llms.txt
|
|
25
|
+
- Sitemap: ${SITE_CONFIG.url}/sitemap.xml
|
|
26
|
+
- Repository: ${LLMS_DATA.repository}
|
|
27
|
+
|
|
28
|
+
## Tech Stack
|
|
29
|
+
|
|
30
|
+
${LLMS_DATA.techStack.map((item) => `- ${item}`).join("\n")}
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
${LLMS_DATA.features.map((item) => `- ${item}`).join("\n")}
|
|
35
|
+
|
|
36
|
+
## Folder Structure
|
|
37
|
+
|
|
38
|
+
\`\`\`
|
|
39
|
+
${LLMS_DATA.folderStructure}
|
|
40
|
+
\`\`\`
|
|
41
|
+
|
|
42
|
+
## Development Commands
|
|
43
|
+
|
|
44
|
+
| Command | Description |
|
|
45
|
+
|---------|-------------|
|
|
46
|
+
| \`${LLMS_DATA.commands.dev}\` | Start development server |
|
|
47
|
+
| \`${LLMS_DATA.commands.build}\` | Build for production |
|
|
48
|
+
| \`${LLMS_DATA.commands.start}\` | Start production server |
|
|
49
|
+
| \`${LLMS_DATA.commands.lint}\` | Lint code |
|
|
50
|
+
| \`${LLMS_DATA.commands.format}\` | Format code |
|
|
51
|
+
|
|
52
|
+
${LLMS_DATA.seoDetails}
|
|
53
|
+
|
|
54
|
+
${LLMS_DATA.conventions}
|
|
55
|
+
|
|
56
|
+
## Environment Variables
|
|
57
|
+
|
|
58
|
+
| Variable | Description |
|
|
59
|
+
|----------|-------------|
|
|
60
|
+
| \`NEXT_PUBLIC_SITE_URL\` | Base URL for sitemap, robots.txt, and Open Graph |
|
|
61
|
+
|
|
62
|
+
## Key Files
|
|
63
|
+
|
|
64
|
+
### src/data/site.ts
|
|
65
|
+
Contains site configuration that powers SEO metadata:
|
|
66
|
+
- name, url, ogImage
|
|
67
|
+
- tagline, description, shortDescription
|
|
68
|
+
- twitterHandle, keywords
|
|
69
|
+
|
|
70
|
+
### src/lib/seo.ts
|
|
71
|
+
SEO utility functions:
|
|
72
|
+
- getMetadata() - Root layout metadata
|
|
73
|
+
- getPageMetadata() - Per-page metadata
|
|
74
|
+
- getViewport() - Viewport with theme colors
|
|
75
|
+
- JSON-LD generators for structured data
|
|
76
|
+
|
|
77
|
+
### src/app/layout.tsx
|
|
78
|
+
Root layout with:
|
|
79
|
+
- Metadata exports
|
|
80
|
+
- Theme provider
|
|
81
|
+
- Geist font configuration
|
|
82
|
+
|
|
83
|
+
### src/components/theme-toggle.tsx
|
|
84
|
+
Dark/light mode toggle component using next-themes.
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
return new Response(content, {
|
|
92
|
+
headers: {
|
|
93
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
94
|
+
"Cache-Control": "public, max-age=3600, s-maxage=86400",
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SITE_CONFIG } from "@/config/site";
|
|
2
|
+
import LLMS_DATA from "@/data/llms";
|
|
3
|
+
|
|
4
|
+
export const dynamic = "force-static";
|
|
5
|
+
|
|
6
|
+
export function GET() {
|
|
7
|
+
const content = `# ${SITE_CONFIG.name}
|
|
8
|
+
|
|
9
|
+
> ${SITE_CONFIG.tagline}
|
|
10
|
+
|
|
11
|
+
${SITE_CONFIG.description}
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
\`\`\`bash
|
|
16
|
+
${LLMS_DATA.quickStart}
|
|
17
|
+
\`\`\`
|
|
18
|
+
|
|
19
|
+
## Resources
|
|
20
|
+
|
|
21
|
+
- Full documentation: ${SITE_CONFIG.url}/llms-full.txt
|
|
22
|
+
- Sitemap: ${SITE_CONFIG.url}/sitemap.xml
|
|
23
|
+
- Repository: ${LLMS_DATA.repository}
|
|
24
|
+
|
|
25
|
+
## Tech Stack
|
|
26
|
+
|
|
27
|
+
${LLMS_DATA.techStack.map((item) => `- ${item}`).join("\n")}
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
${LLMS_DATA.features.map((item) => `- ${item}`).join("\n")}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
return new Response(content, {
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
37
|
+
"Cache-Control": "public, max-age=3600, s-maxage=86400",
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next";
|
|
2
|
+
|
|
3
|
+
import { SITE_CONFIG } from "@/config/site";
|
|
4
|
+
|
|
5
|
+
export default function manifest(): MetadataRoute.Manifest {
|
|
6
|
+
return {
|
|
7
|
+
name: SITE_CONFIG.name,
|
|
8
|
+
short_name: SITE_CONFIG.name,
|
|
9
|
+
description: SITE_CONFIG.description,
|
|
10
|
+
icons: [
|
|
11
|
+
{
|
|
12
|
+
src: "/favicon/favicon.svg",
|
|
13
|
+
type: "image/svg+xml",
|
|
14
|
+
sizes: "any",
|
|
15
|
+
purpose: "any",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
src: "/favicon/web-app-manifest-192x192.png",
|
|
19
|
+
type: "image/png",
|
|
20
|
+
sizes: "192x192",
|
|
21
|
+
purpose: "any",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
src: "/favicon/web-app-manifest-512x512.png",
|
|
25
|
+
type: "image/png",
|
|
26
|
+
sizes: "512x512",
|
|
27
|
+
purpose: "maskable",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
id: "/?utm_source=pwa",
|
|
31
|
+
start_url: "/?utm_source=pwa",
|
|
32
|
+
display: "standalone",
|
|
33
|
+
scope: "/",
|
|
34
|
+
screenshots: [
|
|
35
|
+
{
|
|
36
|
+
src: "/images/screenshot-mobile-dark.webp",
|
|
37
|
+
type: "image/webp",
|
|
38
|
+
sizes: "440x956",
|
|
39
|
+
form_factor: "narrow",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
src: "/images/screenshot-mobile-light.webp",
|
|
43
|
+
type: "image/webp",
|
|
44
|
+
sizes: "440x956",
|
|
45
|
+
form_factor: "narrow",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
src: "/images/screenshot-desktop-dark.webp",
|
|
49
|
+
type: "image/webp",
|
|
50
|
+
sizes: "1920x1080",
|
|
51
|
+
form_factor: "wide",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
src: "/images/screenshot-desktop-light.webp",
|
|
55
|
+
type: "image/webp",
|
|
56
|
+
sizes: "1920x1080",
|
|
57
|
+
form_factor: "wide",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { SITE_CONFIG } from "@/config/site";
|
|
3
|
+
import { getPageMetadata } from "@/lib/seo";
|
|
4
|
+
import { BlogSection } from "@/blogs/components/blog-section";
|
|
5
|
+
import { CopyableCode } from "@/components/copyable-code";
|
|
6
|
+
import { Github } from "lucide-react";
|
|
7
|
+
|
|
8
|
+
export const metadata = getPageMetadata({
|
|
9
|
+
title: `${SITE_CONFIG.name} - ${SITE_CONFIG.tagline}`,
|
|
10
|
+
path: "/",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export default function Home() {
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<section className="container mx-auto flex max-w-5xl flex-col items-center justify-center px-4 py-10 text-center sm:py-16">
|
|
17
|
+
<div className="space-y-6">
|
|
18
|
+
<h1 className="text-3xl font-bold tracking-tight text-foreground sm:text-5xl sm:leading-tight">
|
|
19
|
+
{SITE_CONFIG.tagline}
|
|
20
|
+
</h1>
|
|
21
|
+
|
|
22
|
+
<p className="mx-auto max-w-2xl text-lg text-muted-foreground sm:text-xl">
|
|
23
|
+
{SITE_CONFIG.shortDescription}
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<div className="flex flex-col items-center justify-center gap-4 sm:flex-row">
|
|
27
|
+
<Link
|
|
28
|
+
href="https://github.com/audoralabs/nextblog-starter"
|
|
29
|
+
target="_blank"
|
|
30
|
+
rel="noopener noreferrer"
|
|
31
|
+
className="inline-flex h-10 items-center justify-center gap-2 rounded-md bg-primary px-6 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
32
|
+
>
|
|
33
|
+
<Github className="h-4 w-4" suppressHydrationWarning />
|
|
34
|
+
Star on GitHub
|
|
35
|
+
</Link>
|
|
36
|
+
<Link
|
|
37
|
+
href="https://www.npmjs.com/package/create-audora-next"
|
|
38
|
+
target="_blank"
|
|
39
|
+
rel="noopener noreferrer"
|
|
40
|
+
className="inline-flex h-10 items-center justify-center rounded-md border border-input bg-background px-6 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
41
|
+
>
|
|
42
|
+
View on npm
|
|
43
|
+
</Link>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div>
|
|
47
|
+
<CopyableCode code="bunx create-audora-next@latest -blog my-blog" />
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</section>
|
|
51
|
+
|
|
52
|
+
<section className="border-t border-border/40 p-2">
|
|
53
|
+
<BlogSection />
|
|
54
|
+
</section>
|
|
55
|
+
</>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next";
|
|
2
|
+
|
|
3
|
+
import { SITE_CONFIG } from "@/config/site";
|
|
4
|
+
|
|
5
|
+
export default function robots(): MetadataRoute.Robots {
|
|
6
|
+
return {
|
|
7
|
+
rules: [
|
|
8
|
+
{
|
|
9
|
+
userAgent: "*",
|
|
10
|
+
allow: "/",
|
|
11
|
+
disallow: ["/api/", "/_next/", "/private/"],
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
sitemap: `${SITE_CONFIG.url}/sitemap.xml`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { MetadataRoute } from "next";
|
|
2
|
+
import { SITE_CONFIG } from "@/config/site";
|
|
3
|
+
import { getBlogPosts } from "@/blogs/data/mdx";
|
|
4
|
+
|
|
5
|
+
export const dynamic = "force-static";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generates a comprehensive sitemap for the website
|
|
9
|
+
*
|
|
10
|
+
* Priority guidelines:
|
|
11
|
+
* - 1.0: Homepage (most important)
|
|
12
|
+
* - 0.8-0.9: Main pages (About, Blog listing, etc.)
|
|
13
|
+
* - 0.6-0.7: Blog posts, category pages
|
|
14
|
+
* - 0.4-0.5: Archive pages, tag pages
|
|
15
|
+
* - 0.3: Less important pages
|
|
16
|
+
*
|
|
17
|
+
* Change frequency guidelines:
|
|
18
|
+
* - always: Pages that change with every access
|
|
19
|
+
* - hourly: Pages that change hourly
|
|
20
|
+
* - daily: Pages that change daily
|
|
21
|
+
* - weekly: Pages that change weekly
|
|
22
|
+
* - monthly: Pages that change monthly
|
|
23
|
+
* - yearly: Pages that change yearly
|
|
24
|
+
* - never: Archived pages
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
28
|
+
const siteUrl = SITE_CONFIG.url;
|
|
29
|
+
const currentDate = new Date().toISOString();
|
|
30
|
+
|
|
31
|
+
// Static routes with priorities and change frequencies
|
|
32
|
+
const staticRoutes: MetadataRoute.Sitemap = [
|
|
33
|
+
{
|
|
34
|
+
url: siteUrl,
|
|
35
|
+
lastModified: currentDate,
|
|
36
|
+
changeFrequency: "weekly",
|
|
37
|
+
priority: 1.0,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// Blog posts
|
|
42
|
+
const blogPosts: MetadataRoute.Sitemap = getBlogPosts().map((post) => ({
|
|
43
|
+
url: `${siteUrl}/blog/${post.slug}`,
|
|
44
|
+
lastModified: post.metadata?.publishedAt
|
|
45
|
+
? new Date(post.metadata.publishedAt).toISOString()
|
|
46
|
+
: currentDate,
|
|
47
|
+
changeFrequency: "monthly" as const,
|
|
48
|
+
priority: 0.7,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
return [...staticRoutes, ...blogPosts];
|
|
52
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { BlogPostCard } from "./blog-post-card";
|
|
2
|
+
// import { AnimateIn } from "@/components/animate-in";
|
|
3
|
+
|
|
4
|
+
interface BlogPostDisplayProps {
|
|
5
|
+
slug: string;
|
|
6
|
+
metadata: {
|
|
7
|
+
title: string;
|
|
8
|
+
publishedAt: string;
|
|
9
|
+
summary: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface AnimatedBlogListProps {
|
|
14
|
+
posts: BlogPostDisplayProps[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function AnimatedBlogList({ posts }: AnimatedBlogListProps) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex flex-col gap-4">
|
|
20
|
+
{posts.map((post, index) => (
|
|
21
|
+
// <AnimateIn key={post.slug} delay={index * 0.05}>
|
|
22
|
+
<BlogPostCard
|
|
23
|
+
key={post.slug}
|
|
24
|
+
slug={post.slug}
|
|
25
|
+
title={post.metadata.title}
|
|
26
|
+
publishedAt={post.metadata.publishedAt}
|
|
27
|
+
summary={post.metadata.summary}
|
|
28
|
+
/>
|
|
29
|
+
// </AnimateIn>
|
|
30
|
+
))}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { Calendar, ChevronRight } from "lucide-react";
|
|
3
|
+
import { formatDateDisplay } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
type BlogPostCardProps = {
|
|
6
|
+
slug: string;
|
|
7
|
+
title: string;
|
|
8
|
+
publishedAt: string;
|
|
9
|
+
summary?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function BlogPostCard({
|
|
13
|
+
slug,
|
|
14
|
+
title,
|
|
15
|
+
publishedAt,
|
|
16
|
+
summary,
|
|
17
|
+
}: BlogPostCardProps) {
|
|
18
|
+
return (
|
|
19
|
+
<Link
|
|
20
|
+
className="group flex flex-col gap-1 rounded-lg border border-border/50 p-3 transition-all hover:border-border sm:p-4"
|
|
21
|
+
href={`/blogs/${slug}`}
|
|
22
|
+
>
|
|
23
|
+
<div className="flex w-full flex-col gap-1 sm:flex-row sm:items-start sm:justify-between">
|
|
24
|
+
<h2 className="text-lg font-medium tracking-tight text-foreground transition-colors group-hover:text-foreground/80">
|
|
25
|
+
{title}
|
|
26
|
+
</h2>
|
|
27
|
+
<time
|
|
28
|
+
dateTime={publishedAt}
|
|
29
|
+
className="flex shrink-0 items-center gap-1.5 text-sm text-muted-foreground tabular-nums"
|
|
30
|
+
>
|
|
31
|
+
<Calendar className="h-4 w-4 shrink-0" />
|
|
32
|
+
{formatDateDisplay(publishedAt)}
|
|
33
|
+
</time>
|
|
34
|
+
</div>
|
|
35
|
+
{summary && (
|
|
36
|
+
<p className="line-clamp-2 text-sm text-muted-foreground">{summary}</p>
|
|
37
|
+
)}
|
|
38
|
+
<span className="mt-1 inline-flex items-center gap-1 text-sm font-medium text-foreground/70 transition-colors group-hover:text-foreground">
|
|
39
|
+
Read article
|
|
40
|
+
<ChevronRight className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
|
|
41
|
+
</span>
|
|
42
|
+
</Link>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default BlogPostCard;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getBlogPosts } from "@/blogs/data/mdx";
|
|
2
|
+
import { ViewAllLink } from "@/components/view-all-link";
|
|
3
|
+
import SectionHeading from "@/components/section-heading";
|
|
4
|
+
import { AnimatedBlogList } from "./animated-blog-list";
|
|
5
|
+
|
|
6
|
+
export function BlogSection() {
|
|
7
|
+
const allBlogs = getBlogPosts();
|
|
8
|
+
|
|
9
|
+
const sorted = [...allBlogs]
|
|
10
|
+
.sort(
|
|
11
|
+
(a, b) =>
|
|
12
|
+
new Date(b.metadata.publishedAt).getTime() -
|
|
13
|
+
new Date(a.metadata.publishedAt).getTime(),
|
|
14
|
+
)
|
|
15
|
+
.slice(0, 4);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<section id="blog" className="pt-4 pb-2">
|
|
19
|
+
<div className="container mx-auto mt-8 flex max-w-(--content-max-width) flex-col gap-6 px-4">
|
|
20
|
+
<SectionHeading>Blogs</SectionHeading>
|
|
21
|
+
{sorted.length === 0 ? (
|
|
22
|
+
<p className="text-muted-foreground">No posts yet.</p>
|
|
23
|
+
) : (
|
|
24
|
+
<AnimatedBlogList posts={sorted} />
|
|
25
|
+
)}
|
|
26
|
+
<div className="flex justify-center pt-2">
|
|
27
|
+
<ViewAllLink href="/blogs" />
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</section>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default BlogSection;
|