2025-12-22 22:51:18 +05:30

357 lines
24 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import axios from 'axios';
import Link from 'next/link';
import IconArrowWaveLeftUp from '@/components/icon/icon-arrow-wave-left-up';
import IconRefresh from '@/components/icon/icon-refresh';
import IconCopy from '@/components/icon/icon-copy';
import IconDownload from '@/components/icon/icon-download';
import Loading from '@/components/layouts/loading';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
const BlogGenerator = () => {
const [url, setUrl] = useState('');
const [loading, setLoading] = useState(false);
const [blogData, setBlogData] = useState<any>(null);
const [error, setError] = useState('');
const [recentBlogs, setRecentBlogs] = useState<any[]>([]);
const [selectedEngine, setEngine] = useState('seomagnifier');
useEffect(() => {
fetchRecentBlogs();
}, [url]);
const fetchRecentBlogs = async () => {
try {
const { data } = await axios.get(`http://localhost:3020/api/blog/recent${url ? `?url=${encodeURIComponent(url)}` : ''}`);
if (data.ok) {
setRecentBlogs(data.data);
}
} catch (err) {
console.error('Error fetching recent blogs:', err);
}
};
const handleGenerate = async () => {
if (!url) return setError('Please enter a website URL');
setLoading(true);
setError('');
setBlogData(null);
try {
const { data } = await axios.post('http://localhost:3020/api/blog/generate', {
url,
engine: selectedEngine
});
if (data.ok) {
setBlogData(data.data);
fetchRecentBlogs();
} else {
setError('Failed to generate content');
}
} catch (err: any) {
setError(err.response?.data?.error || err.message || 'Something went wrong');
} finally {
setLoading(false);
}
};
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
alert('Copied to clipboard!');
};
return (
<div className="pb-10 lg:flex gap-6 max-w-[1600px] mx-auto">
<div className="flex-1 min-w-0">
{/* Header Section */}
<div className="relative rounded-t-md bg-primary-light bg-[url('/assets/images/knowledge/pattern.png')] bg-contain bg-left-top bg-no-repeat px-5 py-10 dark:bg-black md:px-10">
<div className="relative z-[1]">
<div className="flex flex-col items-center justify-center sm:-ms-32 sm:flex-row xl:-ms-60">
<div className="mb-2 flex gap-1 text-end text-base leading-5 sm:flex-col xl:text-xl">
<span>One Click</span>
<span>Expert Content</span>
</div>
<div className="me-4 ms-2 hidden text-[#0E1726] rtl:rotate-y-180 dark:text-white sm:block">
<IconArrowWaveLeftUp className="w-16 xl:w-28" />
</div>
<div className="mb-2 text-center text-2xl font-bold dark:text-white md:text-5xl">AI Blog Generator</div>
</div>
<p className="mb-9 text-center text-base font-semibold">Transform any website URL into an SEO-optimized, human-readable blog post in seconds.</p>
<div className="relative mx-auto max-w-[700px] space-y-4">
<div className="relative">
<input
type="text"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="Enter Website URL (e.g., https://example.com)"
className="form-input py-4 ltr:pr-[120px] rtl:pl-[120px] shadow-lg rounded-full border-2 border-primary/20 focus:border-primary transition-all text-lg"
/>
<button
type="button"
onClick={handleGenerate}
disabled={loading}
className={`btn btn-primary absolute top-1.5 ltr:right-1.5 rtl:left-1.5 rounded-full py-2.5 px-6 shadow-none flex items-center gap-2 ${loading ? 'bg-gray-400 opacity-70' : 'bg-primary'}`}
>
{loading ? (
<>
<span className="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"></span>
Analyzing...
</>
) : (
'Generate Blog'
)}
</button>
</div>
<div className="flex flex-wrap items-center justify-center gap-4 animate-fade-in">
<span className="text-xs font-bold uppercase text-gray-400 tracking-wider">AI Engine:</span>
<div className="flex bg-gray-100 dark:bg-dark p-1 rounded-xl border border-gray-200 dark:border-gray-800">
{[
{ id: 'seomagnifier', name: 'SEO Magnifier', icon: '🆓', sub: 'Unlimited' },
{ id: 'ollama', name: 'Ollama', icon: '🏠', sub: 'Local' },
{ id: 'openai', name: 'OpenAI', icon: '💎', sub: 'Premium' },
].map((engine) => (
<button
key={engine.id}
onClick={() => setEngine(engine.id)}
className={`flex flex-col items-center px-4 py-2 rounded-lg transition-all ${selectedEngine === engine.id
? 'bg-white dark:bg-gray-700 shadow-md text-primary scale-105'
: 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
}`}
>
<span className="text-lg">{engine.icon}</span>
<span className="text-[10px] font-bold uppercase">{engine.name}</span>
<span className="text-[8px] opacity-70">{engine.sub}</span>
</button>
))}
</div>
</div>
</div>
{error && <p className="text-red-500 mt-4 text-center font-medium animate-bounce">{error}</p>}
</div>
</div>
<div className="px-5 md:px-10 mt-10">
{!blogData && !loading && (
<div className="flex flex-col items-center justify-center py-20 text-center opacity-40">
<div className="bg-gray-200 p-6 rounded-full mb-4">
<IconRefresh className="w-12 h-12" />
</div>
<p className="text-xl font-medium">Enter a URL above to start the magic.</p>
<p className="text-sm">We'll crawl the site, analyze its SEO structure, and build a custom blog for you.</p>
</div>
)}
{loading && (
<div className="flex flex-col items-center justify-center py-20">
<div className="loader ring-4 ring-primary border-4 border-transparent border-t-primary w-16 h-16 rounded-full animate-spin"></div>
<p className="mt-6 text-xl font-bold animate-pulse">Deep Auditing Website Structure...</p>
<p className="text-gray-500">Extracting semantic markers and entity relationships...</p>
</div>
)}
{blogData && (
<div className="grid grid-cols-1 xl:grid-cols-12 gap-6">
{/* Main Blog Content Section */}
<div className="xl:col-span-8 space-y-6">
<div className="panel shadow-xl border-t-4 border-primary">
<div className="flex items-center justify-between mb-6 pb-4 border-b">
<h2 className="text-2xl font-bold text-gray-800 dark:text-white-light">Generated Blog Post</h2>
<div className="flex gap-2">
<Link href={`/blog/${blogData._id}`} className="btn btn-primary btn-sm px-3 py-2 flex items-center gap-2">
View Full View
</Link>
<button onClick={() => copyToClipboard(blogData.content)} className="btn btn-outline-primary btn-sm px-3 py-2 flex items-center gap-2">
<IconCopy className="w-4 h-4" /> Copy Content
</button>
</div>
</div>
<div className="prose prose-blue dark:prose-invert max-w-none">
<div className="mb-8 p-4 bg-gray-50 dark:bg-dark rounded-lg border-s-4 border-primary italic text-gray-600 dark:text-gray-400">
<p className="font-bold text-primary mb-1">SEO Blueprint Info:</p>
<ul className="text-sm space-y-1">
<li><strong>Meta Title:</strong> {blogData.metaTitle}</li>
<li><strong>Meta Description:</strong> {blogData.metaDescription}</li>
<li><strong>Focus Keyword:</strong> {blogData.focusKeyword}</li>
</ul>
</div>
{blogData.generatedImageUrls?.[0] && (
<div className="mb-8 rounded-2xl overflow-hidden shadow-2xl relative group">
<img src={blogData.generatedImageUrls[0]} alt="Generated blog header" className="w-full h-[400px] object-cover transition-transform duration-500 group-hover:scale-105" />
<div className="absolute top-4 right-4 badge badge-primary bg-primary/80 backdrop-blur-md border-none">AI Generated</div>
</div>
)}
<h1 className="text-4xl lg:text-5xl font-extrabold mb-10 leading-tight text-black dark:text-white">{blogData.title}</h1>
<div className="prose prose-lg dark:prose-invert max-w-none
prose-p:text-lg prose-p:leading-relaxed prose-p:text-gray-700 dark:prose-p:text-gray-300
prose-h2:text-3xl prose-h2:mt-12 prose-h2:mb-6 prose-h2:font-bold
prose-h3:text-2xl prose-h3:mt-8 prose-h3:font-bold
prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:py-4 prose-blockquote:px-8 prose-blockquote:rounded-r-2xl prose-blockquote:italic">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{blogData.content}
</ReactMarkdown>
</div>
</div>
</div>
{/* Image Prompts Section */}
<div className="panel shadow-lg">
<h3 className="text-xl font-bold mb-4 flex items-center gap-2">
<span className="p-2 bg-secondary/10 text-secondary rounded">
🎨
</span>
AI Image Prompts
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{blogData.imagePrompts.map((prompt: string, i: number) => (
<div key={i} className="p-4 bg-gray-50 dark:bg-dark rounded-xl border border-gray-200 dark:border-gray-800 relative group">
<p className="text-sm italic text-gray-700 dark:text-gray-300">"{prompt}"</p>
<button
onClick={() => copyToClipboard(prompt)}
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity bg-white dark:bg-gray-800 p-1 rounded shadow"
title="Copy prompt"
>
<IconCopy className="w-4 h-4" />
</button>
</div>
))}
</div>
</div>
</div>
{/* Sidebar Metrics/Analysis Section */}
<div className="xl:col-span-4 space-y-6">
<div className="panel shadow-lg bg-gradient-to-br from-primary/5 to-transparent">
<h3 className="text-xl font-bold mb-4">Content Quality</h3>
<div className="space-y-6">
<div>
<div className="flex justify-between mb-2">
<span className="font-semibold">SEO Optimization</span>
<span className="text-primary font-bold">{blogData.seoScore}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div className="bg-primary h-2.5 rounded-full shadow-[0_0_10px_rgba(67,97,238,0.5)] transition-all duration-1000" style={{ width: `${blogData.seoScore}%` }}></div>
</div>
</div>
<div>
<div className="flex justify-between mb-2">
<span className="font-semibold">AI Detection (Human Score)</span>
<span className="text-info font-bold">{blogData.aiDetectionScore || 92}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div className="bg-info h-2.5 rounded-full shadow-[0_0_10px_rgba(0,186,211,0.5)] transition-all duration-1000" style={{ width: `${blogData.aiDetectionScore || 92}%` }}></div>
</div>
</div>
<div>
<div className="flex justify-between mb-2">
<span className="font-semibold">Human Readability</span>
<span className="text-secondary font-bold">{blogData.readabilityScore}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div className="bg-secondary h-2.5 rounded-full shadow-[0_0_10px_rgba(128,94,255,0.5)] transition-all duration-1000" style={{ width: `${blogData.readabilityScore}%` }}></div>
</div>
</div>
<div className="pt-4 mt-2 border-t border-dashed border-gray-200 dark:border-gray-700">
<div className="flex justify-between items-center bg-warning/10 p-3 rounded-lg border border-warning/20">
<span className="font-black text-[10px] uppercase text-warning-dark tracking-widest">Focus Keyword</span>
<span className="text-xs font-bold text-warning-dark truncate max-w-[150px]">{blogData.focusKeyword || 'AI Optimized'}</span>
</div>
</div>
{blogData.seoDetails && (
<div className="mt-4 pt-4 border-t border-gray-100 dark:border-gray-800">
<h4 className="text-xs font-bold text-gray-500 uppercase mb-3">SEO Checklist</h4>
<ul className="space-y-2">
{blogData.seoDetails.map((detail: string, i: number) => (
<li key={i} className="text-[11px] flex items-center gap-2">
<span className={`w-1.5 h-1.5 rounded-full ${detail.toLowerCase().includes('missing') ? 'bg-red-500' : 'bg-green-500'}`}></span>
<span className={detail.toLowerCase().includes('missing') ? 'text-red-500' : 'text-gray-600 dark:text-gray-400 font-medium'}>{detail}</span>
</li>
))}
</ul>
</div>
)}
</div>
</div>
<div className="panel shadow-lg">
<h3 className="text-xl font-bold mb-4">Internal Link Strategy</h3>
<p className="text-sm text-gray-500 mb-4">We've identified the high-authority endpoints to link from this blog post:</p>
<div className="space-y-3">
<div className="p-3 bg-gray-50 dark:bg-dark rounded border-l-2 border-primary">
<p className="text-xs font-bold text-primary uppercase">Primary Anchor</p>
<p className="text-sm font-medium">Link to "Solutions" page</p>
</div>
<div className="p-3 bg-gray-50 dark:bg-dark rounded border-l-2 border-secondary">
<p className="text-xs font-bold text-secondary uppercase">Secondary Anchor</p>
<p className="text-sm font-medium">Link to "Our Process" deep dive</p>
</div>
</div>
</div>
<div className="panel bg-black text-white p-6 rounded-2xl shadow-2xl relative overflow-hidden group">
<div className="absolute -top-10 -right-10 w-40 h-40 bg-primary/20 rounded-full blur-3xl group-hover:scale-150 transition-all duration-700"></div>
<h4 className="text-2xl font-bold mb-4 relative z-[1]">Power Up Your Content</h4>
<p className="text-gray-400 mb-6 relative z-[1]">Upgrade to Pro to unlock unlimited crawls and 100% genuine Gemini-powered content.</p>
<button className="btn btn-primary w-full py-4 text-lg font-bold relative z-[1] shadow-[0_0_20px_rgba(67,97,238,0.3)]">
Upgrade Now
</button>
</div>
</div>
</div>
)}
</div>
</div>
{/* Recently Generated Blogs Sidebar */}
<div className="lg:w-[350px] shrink-0 space-y-6 mt-10 lg:mt-0 px-5 lg:px-0">
<div className="panel shadow-lg h-fit sticky top-24">
<div className="flex items-center justify-between mb-4 pb-2 border-b">
<h3 className="text-xl font-bold">Recent Blogs</h3>
<button type="button" onClick={fetchRecentBlogs} className="hover:rotate-180 transition-all duration-500 text-primary">
<IconRefresh className="w-4 h-4" />
</button>
</div>
{recentBlogs.length === 0 ? (
<p className="text-sm text-gray-500 text-center py-10">No blogs generated yet {url ? 'for this URL' : ''}.</p>
) : (
<div className="space-y-4 max-h-[70vh] overflow-y-auto pr-2 custom-scrollbar">
{recentBlogs.map((blog) => (
<div
key={blog._id}
className={`p-3 rounded-lg border cursor-pointer transition-all hover:border-primary group ${blogData?._id === blog._id ? 'bg-primary/5 border-primary' : 'bg-gray-50 dark:bg-dark border-transparent'}`}
onClick={() => setBlogData(blog)}
>
<h4 className="font-bold text-sm line-clamp-2 group-hover:text-primary transition-colors">{blog.title}</h4>
<div className="flex items-center justify-between mt-2">
<span className="text-[10px] text-gray-400">{new Date(blog.createdAt).toLocaleDateString()}</span>
<span className="text-[10px] px-2 py-0.5 bg-gray-200 dark:bg-gray-800 rounded-full font-medium">SEO: {blog.seoScore}%</span>
</div>
<p className="text-[10px] text-gray-500 mt-1 truncate">{blog.url}</p>
</div>
))}
</div>
)}
<div className="mt-6 pt-4 border-t">
<p className="text-[11px] text-gray-400 text-center">
History is synced to your account.
</p>
</div>
</div>
</div>
</div>
);
};
export default BlogGenerator;