aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authormihna123 <mihailonvojinovic@gmail.com>2026-06-08 14:19:32 +0200
committermihna123 <mihailonvojinovic@gmail.com>2026-06-08 14:19:32 +0200
commit67eb91ad6dccf2a14e3f8d3b285c75d251322973 (patch)
tree648fc2de5568ef8f541e6c7033e3c1a0a953a92e /src/app
parent3f513363524c7cfd9d7fb573454090d2ba01bda9 (diff)
downloadinvoice-67eb91ad6dccf2a14e3f8d3b285c75d251322973.tar.gz
invoice-67eb91ad6dccf2a14e3f8d3b285c75d251322973.zip
feat(blogs): implement blogging functionality
- Implement static blog generation from markdown files
Diffstat (limited to 'src/app')
-rw-r--r--src/app/blog/page.tsx7
-rw-r--r--src/app/blogs/[slug]/page.tsx36
-rw-r--r--src/app/blogs/page.tsx39
-rw-r--r--src/app/globals.css29
4 files changed, 104 insertions, 7 deletions
diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx
deleted file mode 100644
index ff5c415..0000000
--- a/src/app/blog/page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function BlogPage() {
- return (
- <section>
- <h1>Blogs</h1>
- </section>
- );
-}
diff --git a/src/app/blogs/[slug]/page.tsx b/src/app/blogs/[slug]/page.tsx
new file mode 100644
index 0000000..3372bea
--- /dev/null
+++ b/src/app/blogs/[slug]/page.tsx
@@ -0,0 +1,36 @@
+import { getBlogFromSlug, markdownToHtml } from '@/lib/blogs/api';
+
+type Params = {
+ params: Promise<{ slug: string }>;
+};
+
+export default async function Blog(props: Params) {
+ const params = await props.params;
+ const blog = getBlogFromSlug(params.slug);
+ const htmlContent = await markdownToHtml(blog.content);
+
+ return (
+ <section className="mt-10 flex justify-center">
+ <div className="w-5xl space-y-4">
+ <h1 className="m-0 mb-2 text-4xl font-bold">{blog.title}</h1>
+ <p>
+ by{' '}
+ <a
+ href={blog.author.social}
+ target="_blank"
+ rel="noopener noreferrerer"
+ className="link"
+ >
+ {blog.author.name}
+ </a>{' '}
+ ~ {blog.date.toLocaleDateString()}
+ </p>
+ <p className="mb-10 text-lg">{blog.excerpt}</p>
+ <article
+ className="markdown"
+ dangerouslySetInnerHTML={{ __html: htmlContent }}
+ />
+ </div>
+ </section>
+ );
+}
diff --git a/src/app/blogs/page.tsx b/src/app/blogs/page.tsx
new file mode 100644
index 0000000..8fa3c42
--- /dev/null
+++ b/src/app/blogs/page.tsx
@@ -0,0 +1,39 @@
+import { type Blog, getAllBlogs } from '@/lib/blogs/api';
+import Link from 'next/link';
+
+const BlogCard = ({ blog }: { blog: Blog }) => {
+ return (
+ <div className="space-y-2">
+ <Link
+ href={`/blogs/${blog.slug}`}
+ className="link text-3xl font-semibold"
+ >
+ {blog.title}
+ </Link>
+ <p>
+ by {blog.author.name} ~ {blog.date.toLocaleDateString()}
+ </p>
+ <p className="text-lg">{blog.excerpt}</p>
+ <Link href={`/blogs/${blog.slug}`} className="link">
+ READ MORE &gt;
+ </Link>
+ </div>
+ );
+};
+
+export default function BlogPage() {
+ const blogs = getAllBlogs();
+ // newest first
+ blogs.sort((a, b) => b.date.getTime() - a.date.getTime());
+
+ return (
+ <section className="mt-10 flex justify-center">
+ <div className="w-5xl space-y-4">
+ <h1 className="text-4xl font-bold">Free Invoice Generator Blogs</h1>
+ {blogs.map((b) => (
+ <BlogCard key={b.slug} blog={b} />
+ ))}
+ </div>
+ </section>
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index eb0ee21..ff84294 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -17,3 +17,32 @@ body {
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
+
+.link,
+.markdown a {
+ @apply text-blue-900 underline hover:bg-blue-900 hover:text-white;
+}
+
+.markdown {
+ @apply space-y-4 text-lg leading-relaxed;
+}
+
+.markdown h1 {
+ @apply text-3xl font-bold;
+}
+
+.markdown h2 {
+ @apply text-2xl font-semibold;
+}
+
+.markdown h3 {
+ @apply text-xl font-semibold;
+}
+
+.markdown ul {
+ @apply list-disc pl-8;
+}
+
+.markdown ol {
+ @apply list-decimal pl-8;
+}