...
This commit is contained in:
parent
51900b05bf
commit
86867b2d5a
@ -38,12 +38,14 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.9",
|
||||
"@radix-ui/react-tooltip": "^1.2.6",
|
||||
"@tailwindcss/vite": "^4.1.7",
|
||||
"buffer": "^6.0.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"framer-motion": "^12.15.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.510.0",
|
||||
"next-themes": "^0.4.6",
|
||||
@ -51,9 +53,11 @@
|
||||
"react-day-picker": "8.10.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hook-form": "^7.56.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-resizable-panels": "^3.0.2",
|
||||
"react-router-dom": "^7.6.1",
|
||||
"recharts": "^2.15.3",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sonner": "^2.0.3",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwindcss": "^4.1.7",
|
||||
|
991
pnpm-lock.yaml
991
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@ import How from './pages/How';
|
||||
import GetStarted from './pages/GetStarted';
|
||||
import Technology from './pages/Technology';
|
||||
import Blog from './pages/Blog';
|
||||
import BlogPost from './pages/BlogPost';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
@ -20,6 +21,7 @@ function App() {
|
||||
<Route path="/get-started" element={<GetStarted />} />
|
||||
<Route path="/technology" element={<Technology />} />
|
||||
<Route path="/blog" element={<Blog />} />
|
||||
<Route path="/blog/:slug" element={<BlogPost />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
|
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "AI That Serves You: The Personal Agent Revolution"
|
||||
author: "HERO Team"
|
||||
date: "December 8, 2024"
|
||||
readTime: "7 min read"
|
||||
tags: ["AI", "Personal Agents", "Technology"]
|
||||
image: "/src/assets/heart.jpg"
|
||||
featured: false
|
||||
draft: false
|
||||
---
|
||||
|
||||
Unlike corporate AI that serves shareholders, Personal Agents work exclusively for you. Learn how this changes everything about AI interaction.
|
||||
|
||||
This is placeholder content for the full article.
|
14
src/blogs/building-trust-in-a-zero-knowledge-world.md
Normal file
14
src/blogs/building-trust-in-a-zero-knowledge-world.md
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "Building Trust in a Zero-Knowledge World"
|
||||
author: "Dr. Sarah Chen"
|
||||
date: "December 12, 2024"
|
||||
readTime: "6 min read"
|
||||
tags: ["Cryptography", "Zero-Knowledge", "Security"]
|
||||
image: "/src/assets/balls.jpg"
|
||||
featured: false
|
||||
draft: false
|
||||
---
|
||||
|
||||
How HERO's zero-knowledge architecture enables trust without compromising privacy. A deep dive into the cryptographic foundations of digital sovereignty.
|
||||
|
||||
This is placeholder content for the full article.
|
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "From Centralized to Sovereign: The Evolution of Digital Identity"
|
||||
author: "Alex Rodriguez"
|
||||
date: "December 10, 2024"
|
||||
readTime: "5 min read"
|
||||
tags: ["Identity", "Blockchain", "Sovereignty"]
|
||||
image: "/src/assets/white_keyb.jpg"
|
||||
featured: false
|
||||
draft: false
|
||||
---
|
||||
|
||||
Tracing the journey from corporate-controlled identities to blockchain-verified, user-owned digital personas. The future is sovereign.
|
||||
|
||||
This is placeholder content for the full article.
|
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "Peer-to-Peer Communication: Cutting Out the Middleman"
|
||||
author: "HERO Team"
|
||||
date: "December 1, 2024"
|
||||
readTime: "6 min read"
|
||||
tags: ["Communication", "P2P", "Privacy"]
|
||||
image: "/src/assets/heart.jpg"
|
||||
featured: false
|
||||
draft: false
|
||||
---
|
||||
|
||||
How HERO enables direct communication between Personal Agents without corporate intermediaries. The future of private messaging is here.
|
||||
|
||||
This is placeholder content for the full article.
|
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "Quantum-Safe Storage: Protecting Your Digital Legacy"
|
||||
author: "Dr. Michael Park"
|
||||
date: "December 5, 2024"
|
||||
readTime: "9 min read"
|
||||
tags: ["Quantum Computing", "Storage", "Security"]
|
||||
image: "/src/assets/balls.jpg"
|
||||
featured: false
|
||||
draft: false
|
||||
---
|
||||
|
||||
As quantum computing threatens traditional encryption, HERO's quantum-safe storage ensures your data remains secure for generations.
|
||||
|
||||
This is placeholder content for the full article.
|
14
src/blogs/the-economics-of-digital-sovereignty.md
Normal file
14
src/blogs/the-economics-of-digital-sovereignty.md
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "The Economics of Digital Sovereignty"
|
||||
author: "Emma Thompson"
|
||||
date: "December 3, 2024"
|
||||
readTime: "4 min read"
|
||||
tags: ["Economics", "Privacy", "Value"]
|
||||
image: "/src/assets/white_keyb.jpg"
|
||||
featured: false
|
||||
draft: false
|
||||
---
|
||||
|
||||
Why paying $20/month for digital freedom is the best investment you'll ever make. Breaking down the true cost of corporate data harvesting.
|
||||
|
||||
This is placeholder content for the full article.
|
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "The Future of Digital Sovereignty: Why Personal Agents Matter"
|
||||
author: "HERO Team"
|
||||
date: "December 15, 2024"
|
||||
readTime: "8 min read"
|
||||
tags: ["Digital Sovereignty", "Privacy", "AI"]
|
||||
image: "/src/assets/heart.jpg"
|
||||
featured: true
|
||||
draft: false
|
||||
---
|
||||
|
||||
In an era where tech giants control our digital lives, Personal Agents represent a fundamental shift toward individual sovereignty and privacy. Discover how HERO is leading this revolution.
|
||||
|
||||
This is placeholder content for the full article.
|
@ -2,6 +2,8 @@ import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
import { Buffer } from 'buffer';
|
||||
window.Buffer = Buffer;
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
|
@ -1,83 +1,111 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Calendar, User, ArrowRight, Tag } from 'lucide-react';
|
||||
import HeroSection from '../components/HeroSection';
|
||||
import Section from '../components/Section';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import matter from 'gray-matter'; // Import gray-matter
|
||||
import { Buffer } from 'buffer'; // Explicitly import Buffer
|
||||
|
||||
// Import images
|
||||
import blogBackground from '../assets/myhero.jpg'; // HERO background with panning effect
|
||||
import post1Image from '../assets/heart.jpg'; // Emotional tech
|
||||
import post2Image from '../assets/balls.jpg'; // AI Agent creation
|
||||
import post3Image from '../assets/white_keyb.jpg'; // Digital privacy
|
||||
import blogBackground from '../assets/myhero.jpg';
|
||||
|
||||
// Use Vite's import.meta.glob to import all markdown files
|
||||
const modules = import.meta.glob('../blogs/*.md', { as: 'raw', eager: true });
|
||||
|
||||
const Blog = () => {
|
||||
const featuredPost = {
|
||||
title: "The Future of Digital Sovereignty: Why Personal Agents Matter",
|
||||
excerpt: "In an era where tech giants control our digital lives, Personal Agents represent a fundamental shift toward individual sovereignty and privacy. Discover how HERO is leading this revolution.",
|
||||
author: "HERO Team",
|
||||
date: "December 15, 2024",
|
||||
readTime: "8 min read",
|
||||
image: post1Image,
|
||||
tags: ["Digital Sovereignty", "Privacy", "AI"]
|
||||
};
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const blogPosts = [
|
||||
{
|
||||
title: "Building Trust in a Zero-Knowledge World",
|
||||
excerpt: "How HERO's zero-knowledge architecture enables trust without compromising privacy. A deep dive into the cryptographic foundations of digital sovereignty.",
|
||||
author: "Dr. Sarah Chen",
|
||||
date: "December 12, 2024",
|
||||
readTime: "6 min read",
|
||||
image: post2Image,
|
||||
tags: ["Cryptography", "Zero-Knowledge", "Security"]
|
||||
},
|
||||
{
|
||||
title: "From Centralized to Sovereign: The Evolution of Digital Identity",
|
||||
excerpt: "Tracing the journey from corporate-controlled identities to blockchain-verified, user-owned digital personas. The future is sovereign.",
|
||||
author: "Alex Rodriguez",
|
||||
date: "December 10, 2024",
|
||||
readTime: "5 min read",
|
||||
image: post3Image,
|
||||
tags: ["Identity", "Blockchain", "Sovereignty"]
|
||||
},
|
||||
{
|
||||
title: "AI That Serves You: The Personal Agent Revolution",
|
||||
excerpt: "Unlike corporate AI that serves shareholders, Personal Agents work exclusively for you. Learn how this changes everything about AI interaction.",
|
||||
author: "HERO Team",
|
||||
date: "December 8, 2024",
|
||||
readTime: "7 min read",
|
||||
image: post1Image,
|
||||
tags: ["AI", "Personal Agents", "Technology"]
|
||||
},
|
||||
{
|
||||
title: "Quantum-Safe Storage: Protecting Your Digital Legacy",
|
||||
excerpt: "As quantum computing threatens traditional encryption, HERO's quantum-safe storage ensures your data remains secure for generations.",
|
||||
author: "Dr. Michael Park",
|
||||
date: "December 5, 2024",
|
||||
readTime: "9 min read",
|
||||
image: post2Image,
|
||||
tags: ["Quantum Computing", "Storage", "Security"]
|
||||
},
|
||||
{
|
||||
title: "The Economics of Digital Sovereignty",
|
||||
excerpt: "Why paying $20/month for digital freedom is the best investment you'll ever make. Breaking down the true cost of corporate data harvesting.",
|
||||
author: "Emma Thompson",
|
||||
date: "December 3, 2024",
|
||||
readTime: "4 min read",
|
||||
image: post3Image,
|
||||
tags: ["Economics", "Privacy", "Value"]
|
||||
},
|
||||
{
|
||||
title: "Peer-to-Peer Communication: Cutting Out the Middleman",
|
||||
excerpt: "How HERO enables direct communication between Personal Agents without corporate intermediaries. The future of private messaging is here.",
|
||||
author: "HERO Team",
|
||||
date: "December 1, 2024",
|
||||
readTime: "6 min read",
|
||||
image: post1Image,
|
||||
tags: ["Communication", "P2P", "Privacy"]
|
||||
}
|
||||
];
|
||||
useEffect(() => {
|
||||
const loadPosts = async () => {
|
||||
try {
|
||||
const loadedPosts = [];
|
||||
for (const path in modules) {
|
||||
const content = modules[path];
|
||||
const { data: frontmatter } = matter(content);
|
||||
|
||||
// Extract slug from filename
|
||||
const slug = path.split('/').pop().replace('.md', '');
|
||||
|
||||
// Only include posts that are not drafts
|
||||
if (frontmatter.draft !== true) {
|
||||
loadedPosts.push({
|
||||
...frontmatter,
|
||||
slug,
|
||||
// Ensure image path is correct for Vite
|
||||
image: frontmatter.image ? new URL(frontmatter.image, import.meta.url).href : '/src/assets/default.jpg'
|
||||
});
|
||||
}
|
||||
}
|
||||
setPosts(loadedPosts);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadPosts();
|
||||
}, []);
|
||||
|
||||
// Handle cases where posts might still be loading or empty
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<HeroSection
|
||||
subtitle="Insights & Updates"
|
||||
title="HERO Blog"
|
||||
description="Loading blog posts..."
|
||||
backgroundImage={blogBackground}
|
||||
ctaText="Subscribe to Updates"
|
||||
ctaLink="#subscribe"
|
||||
showVideo={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<HeroSection
|
||||
subtitle="Insights & Updates"
|
||||
title="HERO Blog"
|
||||
description={`Error: ${error}`}
|
||||
backgroundImage={blogBackground}
|
||||
ctaText="Subscribe to Updates"
|
||||
ctaLink="#subscribe"
|
||||
showVideo={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure posts array is not empty before accessing elements
|
||||
const featuredPost = posts.find(post => post.featured === true);
|
||||
const blogPosts = posts.filter(post => post.slug !== featuredPost?.slug);
|
||||
|
||||
// If no featured post is found, and there are other posts, pick the first one
|
||||
const displayFeaturedPost = featuredPost || (posts.length > 0 ? posts[0] : null);
|
||||
|
||||
if (!displayFeaturedPost && posts.length === 0) {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<HeroSection
|
||||
subtitle="Insights & Updates"
|
||||
title="HERO Blog"
|
||||
description="No blog posts found."
|
||||
backgroundImage={blogBackground}
|
||||
ctaText="Subscribe to Updates"
|
||||
ctaLink="#subscribe"
|
||||
showVideo={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const categories = [
|
||||
"All Posts",
|
||||
@ -112,75 +140,79 @@ const Blog = () => {
|
||||
</div>
|
||||
|
||||
{/* Featured Post Section */}
|
||||
<Section background="gradient" padding="xlarge">
|
||||
<div className="text-center mb-16">
|
||||
<motion.h2
|
||||
className="text-4xl md:text-5xl font-bold text-white mb-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
Featured <span className="text-blue-400">Article</span>
|
||||
</motion.h2>
|
||||
</div>
|
||||
|
||||
<motion.article
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-4xl mx-auto glass-effect rounded-2xl overflow-hidden hover:border-blue-400/30 transition-all duration-300 hover-lift"
|
||||
>
|
||||
<div className="md:flex">
|
||||
<div className="md:w-1/2">
|
||||
<img
|
||||
src={featuredPost.image}
|
||||
alt={featuredPost.title}
|
||||
className="w-full h-64 md:h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="md:w-1/2 p-8">
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{featuredPost.tags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-3 py-1 bg-blue-600/20 text-blue-400 text-sm rounded-full"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h3 className="text-2xl md:text-3xl font-bold text-white mb-4 leading-tight">
|
||||
{featuredPost.title}
|
||||
</h3>
|
||||
|
||||
<p className="text-gray-300 mb-6 leading-relaxed">
|
||||
{featuredPost.excerpt}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-between text-sm text-gray-400 mb-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<User size={16} />
|
||||
<span>{featuredPost.author}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Calendar size={16} />
|
||||
<span>{featuredPost.date}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span>{featuredPost.readTime}</span>
|
||||
</div>
|
||||
|
||||
<Button className="bg-blue-600 hover:bg-blue-700 text-white">
|
||||
Read Full Article <ArrowRight size={16} className="ml-2" />
|
||||
</Button>
|
||||
</div>
|
||||
{displayFeaturedPost && (
|
||||
<Section background="gradient" padding="xlarge">
|
||||
<div className="text-center mb-16">
|
||||
<motion.h2
|
||||
className="text-4xl md:text-5xl font-bold text-white mb-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
Featured <span className="text-blue-400">Article</span>
|
||||
</motion.h2>
|
||||
</div>
|
||||
</motion.article>
|
||||
</Section>
|
||||
|
||||
<Link to={`/blog/${displayFeaturedPost.slug}`} className="block">
|
||||
<motion.article
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-4xl mx-auto glass-effect rounded-2xl overflow-hidden hover:border-blue-400/30 transition-all duration-300 hover-lift"
|
||||
>
|
||||
<div className="md:flex">
|
||||
<div className="md:w-1/2">
|
||||
<img
|
||||
src={displayFeaturedPost.image}
|
||||
alt={displayFeaturedPost.title}
|
||||
className="w-full h-64 md:h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="md:w-1/2 p-8">
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{displayFeaturedPost.tags?.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-3 py-1 bg-blue-600/20 text-blue-400 text-sm rounded-full"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h3 className="text-2xl md:text-3xl font-bold text-white mb-4 leading-tight">
|
||||
{displayFeaturedPost.title}
|
||||
</h3>
|
||||
|
||||
<p className="text-gray-300 mb-6 leading-relaxed">
|
||||
{displayFeaturedPost.excerpt}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-between text-sm text-gray-400 mb-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<User size={16} />
|
||||
<span>{displayFeaturedPost.author}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Calendar size={16} />
|
||||
<span>{displayFeaturedPost.date}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span>{displayFeaturedPost.readTime}</span>
|
||||
</div>
|
||||
|
||||
<span className="inline-flex items-center text-blue-400 hover:text-blue-300 transition-colors duration-300 font-semibold cursor-pointer">
|
||||
Read Full Article <ArrowRight size={16} className="ml-2" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.article>
|
||||
</Link>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
{/* Blog Posts Grid */}
|
||||
<Section background="dark" padding="xlarge">
|
||||
@ -205,10 +237,10 @@ const Blog = () => {
|
||||
>
|
||||
{categories.map((category, index) => (
|
||||
<button
|
||||
key={index}
|
||||
key={category}
|
||||
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-300 ${
|
||||
index === 0
|
||||
? 'bg-purple-600 text-white'
|
||||
index === 0
|
||||
? 'bg-purple-600 text-white'
|
||||
: 'bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-white'
|
||||
}`}
|
||||
>
|
||||
@ -220,58 +252,60 @@ const Blog = () => {
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{blogPosts.map((post, index) => (
|
||||
<motion.article
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className="glass-effect rounded-xl overflow-hidden hover:border-purple-400/30 transition-all duration-300 hover-lift group"
|
||||
>
|
||||
<div className="relative overflow-hidden">
|
||||
<img
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
className="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
<div className="absolute inset-0"></div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{post.tags.slice(0, 2).map((tag, tagIndex) => (
|
||||
<span
|
||||
key={tagIndex}
|
||||
className="px-2 py-1 bg-purple-600/20 text-purple-400 text-xs rounded-full"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
<Link to={`/blog/${post.slug}`} className="block">
|
||||
<motion.article
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className="glass-effect rounded-xl overflow-hidden hover:border-purple-400/30 transition-all duration-300 hover-lift group"
|
||||
>
|
||||
<div className="relative overflow-hidden">
|
||||
<img
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
className="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
<div className="absolute inset-0"></div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-semibold text-white mb-3 leading-tight group-hover:text-purple-100 transition-colors duration-300">
|
||||
{post.title}
|
||||
</h3>
|
||||
|
||||
<p className="text-gray-300 text-sm mb-4 leading-relaxed">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-between text-xs text-gray-400">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center space-x-1">
|
||||
<User size={12} />
|
||||
<span>{post.author}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar size={12} />
|
||||
<span>{post.date}</span>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{post.tags.slice(0, 2).map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="px-2 py-1 bg-purple-600/20 text-purple-400 text-xs rounded-full"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-semibold text-white mb-3 leading-tight group-hover:text-purple-100 transition-colors duration-300">
|
||||
{post.title}
|
||||
</h3>
|
||||
|
||||
<p className="text-gray-300 text-sm mb-4 leading-relaxed">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-between text-xs text-gray-400">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center space-x-1">
|
||||
<User size={12} />
|
||||
<span>{post.author}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar size={12} />
|
||||
<span>{post.date}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span>{post.readTime}</span>
|
||||
</div>
|
||||
<span>{post.readTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.article>
|
||||
</motion.article>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
151
src/pages/BlogPost.jsx
Normal file
151
src/pages/BlogPost.jsx
Normal file
@ -0,0 +1,151 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import matter from 'gray-matter';
|
||||
import { ArrowLeft, Calendar, User, Tag } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Buffer } from 'buffer'; // Explicitly import Buffer
|
||||
|
||||
const BlogPost = () => {
|
||||
const { slug } = useParams();
|
||||
const [post, setPost] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadPost = async () => {
|
||||
try {
|
||||
const response = await fetch(`/src/blogs/${slug}.md`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Post not found');
|
||||
}
|
||||
const content = await response.text();
|
||||
const { data: frontmatter, content: markdownContent } = matter(content);
|
||||
|
||||
setPost({
|
||||
frontmatter,
|
||||
content: markdownContent
|
||||
});
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadPost();
|
||||
}, [slug]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-900 flex items-center justify-center">
|
||||
<div className="text-white text-xl">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-900 flex items-center justify-center">
|
||||
<div className="text-white text-xl">Error: {error}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-900">
|
||||
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
||||
<Link to="/blog">
|
||||
<Button className="mb-8 bg-gray-800 hover:bg-gray-700 text-white">
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Back to Blog
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
{post.frontmatter.image && (
|
||||
<div className="mb-8">
|
||||
<img
|
||||
src={post.frontmatter.image}
|
||||
alt={post.frontmatter.title}
|
||||
className="w-full h-64 md:h-96 object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<article className="bg-gray-800 rounded-lg p-8">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-4xl font-bold text-white mb-4">
|
||||
{post.frontmatter.title}
|
||||
</h1>
|
||||
|
||||
<div className="flex items-center space-x-4 text-gray-400 mb-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<User size={16} />
|
||||
<span>{post.frontmatter.author}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Calendar size={16} />
|
||||
<span>{post.frontmatter.date}</span>
|
||||
</div>
|
||||
<span>{post.frontmatter.readTime}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{post.frontmatter.tags?.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-3 py-1 bg-blue-600/20 text-blue-400 text-sm rounded-full"
|
||||
>
|
||||
<Tag className="inline-block mr-1 h-3 w-3" />
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="prose prose-invert prose-lg max-w-none">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
h1: ({node, ...props}) => <h1 className="text-3xl font-bold text-white mb-4" {...props} />,
|
||||
h2: ({node, ...props}) => <h2 className="text-2xl font-semibold text-white mb-3 mt-6" {...props} />,
|
||||
h3: ({node, ...props}) => <h3 className="text-xl font-semibold text-white mb-2 mt-4" {...props} />,
|
||||
p: ({node, ...props}) => <p className="text-gray-300 leading-relaxed mb-4" {...props} />,
|
||||
ul: ({node, ...props}) => <ul className="text-gray-300 list-disc list-inside mb-4" {...props} />,
|
||||
ol: ({node, ...props}) => <ol className="text-gray-300 list-decimal list-inside mb-4" {...props} />,
|
||||
li: ({node, ...props}) => <li className="text-gray-300 mb-1" {...props} />,
|
||||
blockquote: ({node, ...props}) => (
|
||||
<blockquote
|
||||
className="border-l-4 border-blue-500 pl-4 italic text-gray-400 mb-4"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
code: ({node, inline, ...props}) => (
|
||||
<code
|
||||
className={`${
|
||||
inline
|
||||
? 'bg-gray-700 px-1 py-0.5 rounded text-sm'
|
||||
: 'block bg-gray-700 p-4 rounded text-sm overflow-x-auto'
|
||||
}`}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
a: ({node, ...props}) => (
|
||||
<a
|
||||
className="text-blue-400 hover:text-blue-300 underline"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{post.content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlogPost;
|
@ -6,9 +6,15 @@ import path from 'path'
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(),tailwindcss()],
|
||||
define: {
|
||||
global: 'window',
|
||||
'process.env': {}, // Define process.env as an empty object
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
// Alias 'buffer' to 'buffer/index.js' for browser compatibility
|
||||
'buffer': 'buffer/index.js',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user