diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogArticleCard.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogArticleCard.tsx new file mode 100644 index 0000000..2aa0ff9 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogArticleCard.tsx @@ -0,0 +1,64 @@ +import Link from 'next/link'; +import Image from 'next/image'; +import type { BlogPost } from '../types'; + +interface BlogArticleCardProps { + post: BlogPost; +} + +const categoryColors: Record = { + guides: 'bg-violet-500/10', + tutorials: 'bg-blue-500/10', + 'use-cases': 'bg-pink-500/10', + news: 'bg-emerald-500/10', +}; + +const formatShortDate = (dateString: string): string => { + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + }); +}; + +export const BlogArticleCard = ({ post }: BlogArticleCardProps) => { + const overlayColor = categoryColors[post.category] || 'bg-violet-500/10'; + + return ( + +
+
+ {post.title} +
+ + {post.category} + +
+
+
+

+ {post.title} +

+

+ {post.description} +

+
+ {formatShortDate(post.date)} + + {post.readTime} +
+
+ + ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogBackground.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogBackground.tsx new file mode 100644 index 0000000..39ef1a0 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogBackground.tsx @@ -0,0 +1,8 @@ +export const BlogBackground = () => { + return ( +
+
+
+
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogCategories.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogCategories.tsx new file mode 100644 index 0000000..df52dfd --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogCategories.tsx @@ -0,0 +1,52 @@ +import Link from 'next/link'; + +interface BlogCategoriesProps { + categories: Record; +} + +const categoryColors: Record = { + guides: 'bg-violet-500', + tutorials: 'bg-blue-500', + 'use-cases': 'bg-pink-500', + news: 'bg-emerald-500', + engineering: 'bg-blue-500', + product: 'bg-purple-500', + design: 'bg-green-500', + culture: 'bg-orange-500', +}; + +export const BlogCategories = ({ categories }: BlogCategoriesProps) => { + const entries = Object.entries(categories); + + if (entries.length === 0) { + return null; + } + + return ( +
+

+ Categories +

+ +
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogNewsletter.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogNewsletter.tsx new file mode 100644 index 0000000..9b64d1b --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogNewsletter.tsx @@ -0,0 +1,61 @@ +'use client'; + +import { useState } from 'react'; +import { Mail } from 'lucide-react'; + +export const BlogNewsletter = () => { + const [email, setEmail] = useState(''); + const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle'); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!email) return; + + setStatus('loading'); + + // TODO: Integrate with email service + // For now, just simulate success + await new Promise((resolve) => setTimeout(resolve, 500)); + console.log('Newsletter subscription:', email); + + setStatus('success'); + setEmail(''); + + // Reset status after 3 seconds + setTimeout(() => setStatus('idle'), 3000); + }; + + return ( +
+
+
+
+ +
+

Subscribe to Banatie

+

+ Get the latest technical articles, tutorials, and updates delivered right to your inbox. +

+
+ setEmail(e.target.value)} + placeholder="your@email.com" + disabled={status === 'loading'} + className="block w-full rounded-lg border border-white/10 bg-[#0B0F19] py-2 px-3 text-white text-sm placeholder:text-gray-600 focus:border-violet-500 focus:ring-1 focus:ring-violet-500 focus:outline-none disabled:opacity-50" + /> + +
+
+
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogPageHeader.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogPageHeader.tsx new file mode 100644 index 0000000..2cf8255 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogPageHeader.tsx @@ -0,0 +1,19 @@ +interface BlogPageHeaderProps { + title?: string; +} + +export const BlogPageHeader = ({ title = 'Latest Articles' }: BlogPageHeaderProps) => { + return ( +
+

{title}

+
+ + +
+
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogSearchInput.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogSearchInput.tsx new file mode 100644 index 0000000..fc3117c --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogSearchInput.tsx @@ -0,0 +1,16 @@ +import { Search } from 'lucide-react'; + +export const BlogSearchInput = () => { + return ( +
+
+ +
+ +
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/BlogTags.tsx b/apps/landing/src/app/(landings)/blog/_components/BlogTags.tsx new file mode 100644 index 0000000..e32ea77 --- /dev/null +++ b/apps/landing/src/app/(landings)/blog/_components/BlogTags.tsx @@ -0,0 +1,30 @@ +import Link from 'next/link'; + +interface BlogTagsProps { + tags: string[]; +} + +export const BlogTags = ({ tags }: BlogTagsProps) => { + if (tags.length === 0) { + return null; + } + + return ( +
+

+ Popular Tags +

+
+ {tags.map((tag) => ( + + #{tag} + + ))} +
+
+ ); +}; diff --git a/apps/landing/src/app/(landings)/blog/_components/index.ts b/apps/landing/src/app/(landings)/blog/_components/index.ts index efd67f9..d0f2d6f 100644 --- a/apps/landing/src/app/(landings)/blog/_components/index.ts +++ b/apps/landing/src/app/(landings)/blog/_components/index.ts @@ -13,3 +13,11 @@ export { BlogInfoBox } from './BlogInfoBox'; export { BlogLeadParagraph } from './BlogLeadParagraph'; export { BlogServiceLink } from './BlogServiceLink'; export { BlogPricing } from './BlogPricing'; + +export { BlogBackground } from './BlogBackground'; +export { BlogArticleCard } from './BlogArticleCard'; +export { BlogPageHeader } from './BlogPageHeader'; +export { BlogSearchInput } from './BlogSearchInput'; +export { BlogCategories } from './BlogCategories'; +export { BlogNewsletter } from './BlogNewsletter'; +export { BlogTags } from './BlogTags'; diff --git a/apps/landing/src/app/(landings)/blog/page.tsx b/apps/landing/src/app/(landings)/blog/page.tsx index 9c92d04..c55e4eb 100644 --- a/apps/landing/src/app/(landings)/blog/page.tsx +++ b/apps/landing/src/app/(landings)/blog/page.tsx @@ -1,6 +1,14 @@ import type { Metadata } from 'next'; -import { getAllPosts } from './utils'; -import { BlogCard, BlogBreadcrumbs } from './_components'; +import { getAllPosts, getCategories } from './utils'; +import { + BlogBackground, + BlogArticleCard, + BlogPageHeader, + BlogSearchInput, + BlogCategories, + BlogNewsletter, + BlogTags, +} from './_components'; export const metadata: Metadata = { title: 'Blog | Banatie', @@ -8,31 +16,51 @@ export const metadata: Metadata = { 'Articles, guides, and updates about AI-powered placeholder images.', }; +const defaultTags = ['ai', 'image-generation', 'api', 'midjourney', 'flux']; + export default function BlogPage() { const posts = getAllPosts(); + const categories = getCategories(); return ( -
-
- + <> + +
+
+
+
+ -

Blog

-

- Articles, guides, and updates about AI-powered images. -

+
+ {posts.map((post) => ( + + ))} +
-
- {posts.map((post) => ( - - ))} + {posts.length === 0 && ( +

+ No articles yet. Check back soon! +

+ )} + + {posts.length > 6 && ( +
+ +
+ )} +
+ + +
- - {posts.length === 0 && ( -

- No articles yet. Check back soon! -

- )} -
-
+
+ ); } diff --git a/apps/landing/src/app/(landings)/blog/utils.ts b/apps/landing/src/app/(landings)/blog/utils.ts index 03fbd2b..0802452 100644 --- a/apps/landing/src/app/(landings)/blog/utils.ts +++ b/apps/landing/src/app/(landings)/blog/utils.ts @@ -33,3 +33,13 @@ export const formatDate = (dateString: string): string => { day: 'numeric', }); }; + +export const getCategories = (): Record => { + return blogPosts.reduce( + (acc, post) => { + acc[post.category] = (acc[post.category] || 0) + 1; + return acc; + }, + {} as Record + ); +};