PostGen / frontend /src /pages /PostEditor.jsx
Seth
update
f80e9b3
import React, { useState } from 'react';
import { motion } from 'framer-motion';
import {
Save,
Send,
Eye,
Calendar,
Clock,
Image,
Layers,
FileText,
Video,
Sparkles,
ChevronDown,
Plus,
X,
GripVertical,
RefreshCw,
ThumbsUp,
MessageCircle,
Share2,
MoreHorizontal,
Globe,
Link2,
Hash,
AtSign,
Bold,
Italic,
List,
Smile,
ImagePlus,
Trash2,
ArrowLeft,
Linkedin
} from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Badge } from '@/components/ui/badge';
import { Label } from '@/components/ui/label';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Separator } from '@/components/ui/separator';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import { Calendar as CalendarComponent } from '@/components/ui/calendar';
import { format } from 'date-fns';
import { Link } from 'react-router-dom';
import { createPageUrl } from '@/utils';
const postTypes = [
{ id: 'carousel', name: 'Carousel', icon: Layers },
{ id: 'cover_content', name: 'Cover Image + Content', icon: Image },
{ id: 'content_only', name: 'Content Only', icon: FileText },
{ id: 'webinar', name: 'Webinar Invite', icon: Video },
];
const products = [
{ id: 'ocr', name: 'Document Parsing (OCR)', shortName: 'OCR' },
{ id: 'p2p', name: 'Purchase To Pay', shortName: 'P2P' },
{ id: 'o2c', name: 'Order to Cash', shortName: 'O2C' },
];
export default function PostEditor() {
const [postType, setPostType] = useState('carousel');
const [product, setProduct] = useState('ocr');
const [content, setContent] = useState(`🚀 Transform Your Document Processing with AI-Powered OCR
Are you still manually processing invoices and documents?
Here's how our Intelligent Document Parsing solution can revolutionize your workflow:
✅ 99.5% accuracy in data extraction
✅ Process 1000+ documents per hour
✅ Seamless integration with existing systems
✅ Reduce manual errors by 95%
The future of document automation is here. Ready to transform your business?
#DocumentAutomation #OCR #AITechnology #DigitalTransformation #BusinessEfficiency`);
const [scheduledDate, setScheduledDate] = useState(new Date());
const [scheduledTime, setScheduledTime] = useState('10:00');
const [carouselSlides, setCarouselSlides] = useState([
{ id: 1, title: 'Slide 1', hasImage: true },
{ id: 2, title: 'Slide 2', hasImage: true },
{ id: 3, title: 'Slide 3', hasImage: true },
]);
const [selectedAssets, setSelectedAssets] = useState([
{ id: 1, name: 'OCR_Demo.png', type: 'image' },
{ id: 2, name: 'Workflow_Diagram.png', type: 'image' },
]);
const addSlide = () => {
setCarouselSlides([...carouselSlides, { id: Date.now(), title: `Slide ${carouselSlides.length + 1}`, hasImage: false }]);
};
const removeSlide = (id) => {
setCarouselSlides(carouselSlides.filter(slide => slide.id !== id));
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50/30">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="mb-8"
>
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div className="flex items-center gap-4">
<Link to={createPageUrl('Scheduler')}>
<Button variant="ghost" size="icon" className="h-10 w-10">
<ArrowLeft className="w-5 h-5" />
</Button>
</Link>
<div>
<h1 className="text-2xl font-bold text-slate-900 tracking-tight">
Create Post
</h1>
<p className="text-slate-500 text-sm mt-0.5">
Compose and schedule your LinkedIn content
</p>
</div>
</div>
<div className="flex gap-3">
<Button variant="outline" className="gap-2 border-slate-200">
<Save className="w-4 h-4" />
Save Draft
</Button>
<Button variant="outline" className="gap-2 border-slate-200">
<Eye className="w-4 h-4" />
Preview
</Button>
<Button className="gap-2 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 shadow-lg shadow-blue-500/25">
<Send className="w-4 h-4" />
Schedule Post
</Button>
</div>
</div>
</motion.div>
<div className="grid lg:grid-cols-5 gap-6">
{/* Editor Panel */}
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className="lg:col-span-3 space-y-6"
>
{/* Post Type Selection */}
<Card className="border-0 shadow-lg shadow-slate-200/50">
<CardHeader className="pb-4">
<CardTitle className="text-base font-semibold">Post Type</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{postTypes.map(type => (
<button
key={type.id}
onClick={() => setPostType(type.id)}
className={`p-4 rounded-xl border-2 transition-all ${
postType === type.id
? 'border-blue-500 bg-blue-50'
: 'border-slate-200 hover:border-slate-300 bg-white'
}`}
>
<type.icon className={`w-6 h-6 mx-auto mb-2 ${
postType === type.id ? 'text-blue-600' : 'text-slate-400'
}`} />
<p className={`text-sm font-medium ${
postType === type.id ? 'text-blue-700' : 'text-slate-600'
}`}>
{type.name}
</p>
</button>
))}
</div>
</CardContent>
</Card>
{/* Content Editor */}
<Card className="border-0 shadow-lg shadow-slate-200/50">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<CardTitle className="text-base font-semibold">Content</CardTitle>
<Button variant="outline" size="sm" className="gap-2 text-violet-600 border-violet-200 hover:bg-violet-50">
<Sparkles className="w-4 h-4" />
AI Generate
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
{/* Formatting Toolbar */}
<div className="flex items-center gap-1 p-2 bg-slate-50 rounded-lg">
<Button variant="ghost" size="icon" className="h-8 w-8">
<Bold className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8">
<Italic className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8">
<List className="w-4 h-4" />
</Button>
<Separator orientation="vertical" className="h-6 mx-1" />
<Button variant="ghost" size="icon" className="h-8 w-8">
<Hash className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8">
<AtSign className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8">
<Link2 className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon" className="h-8 w-8">
<Smile className="w-4 h-4" />
</Button>
</div>
<Textarea
value={content}
onChange={(e) => setContent(e.target.value)}
className="min-h-[250px] text-sm leading-relaxed border-slate-200 focus:border-blue-300"
placeholder="Write your post content here..."
/>
<div className="flex items-center justify-between text-sm text-slate-500">
<span>{content.length} / 3000 characters</span>
<Button variant="ghost" size="sm" className="gap-1 text-blue-600">
<RefreshCw className="w-3 h-3" />
Regenerate
</Button>
</div>
</CardContent>
</Card>
{/* Carousel Slides (if carousel type) */}
{postType === 'carousel' && (
<Card className="border-0 shadow-lg shadow-slate-200/50">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<CardTitle className="text-base font-semibold">Carousel Slides</CardTitle>
<Button onClick={addSlide} variant="outline" size="sm" className="gap-1">
<Plus className="w-4 h-4" />
Add Slide
</Button>
</div>
</CardHeader>
<CardContent>
<div className="space-y-3">
{carouselSlides.map((slide, index) => (
<motion.div
key={slide.id}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="flex items-center gap-3 p-3 rounded-xl border border-slate-200 bg-white hover:border-slate-300 transition-colors"
>
<GripVertical className="w-4 h-4 text-slate-400 cursor-grab" />
<div className="w-16 h-16 rounded-lg bg-gradient-to-br from-slate-100 to-slate-50 flex items-center justify-center border border-slate-200">
{slide.hasImage ? (
<Image className="w-6 h-6 text-slate-400" />
) : (
<ImagePlus className="w-6 h-6 text-slate-300" />
)}
</div>
<div className="flex-1">
<Input
value={slide.title}
className="h-8 text-sm border-0 bg-transparent p-0 font-medium"
placeholder="Slide title"
/>
<p className="text-xs text-slate-500 mt-0.5">
{slide.hasImage ? 'Image attached' : 'No image'}
</p>
</div>
<Button variant="ghost" size="sm" className="gap-1 text-blue-600">
<ImagePlus className="w-4 h-4" />
{slide.hasImage ? 'Change' : 'Add'} Image
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => removeSlide(slide.id)}
className="h-8 w-8 text-slate-400 hover:text-red-500"
>
<Trash2 className="w-4 h-4" />
</Button>
</motion.div>
))}
</div>
</CardContent>
</Card>
)}
{/* Media Attachments */}
<Card className="border-0 shadow-lg shadow-slate-200/50">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<CardTitle className="text-base font-semibold">Media from Repository</CardTitle>
<Link to={createPageUrl('Repository')}>
<Button variant="outline" size="sm" className="gap-1">
<Plus className="w-4 h-4" />
Browse Assets
</Button>
</Link>
</div>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-3">
{selectedAssets.map(asset => (
<div
key={asset.id}
className="flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-50 border border-slate-200"
>
<Image className="w-4 h-4 text-blue-500" />
<span className="text-sm text-slate-700">{asset.name}</span>
<Button variant="ghost" size="icon" className="h-5 w-5 text-slate-400 hover:text-red-500">
<X className="w-3 h-3" />
</Button>
</div>
))}
<button className="flex items-center gap-2 px-3 py-2 rounded-lg border-2 border-dashed border-slate-200 text-slate-500 hover:border-blue-300 hover:text-blue-600 transition-colors">
<Plus className="w-4 h-4" />
<span className="text-sm">Add from Canva</span>
</button>
</div>
</CardContent>
</Card>
</motion.div>
{/* Preview & Settings Panel */}
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
className="lg:col-span-2 space-y-6"
>
{/* Schedule Settings */}
<Card className="border-0 shadow-lg shadow-slate-200/50">
<CardHeader className="pb-4">
<CardTitle className="text-base font-semibold flex items-center gap-2">
<Calendar className="w-4 h-4 text-blue-600" />
Schedule
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label className="text-sm text-slate-600">Product Category</Label>
<Select value={product} onValueChange={setProduct}>
<SelectTrigger className="mt-1.5">
<SelectValue />
</SelectTrigger>
<SelectContent>
{products.map(p => (
<SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<Label className="text-sm text-slate-600">Date</Label>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="w-full justify-start mt-1.5 text-left font-normal">
<Calendar className="w-4 h-4 mr-2" />
{format(scheduledDate, 'MMM d, yyyy')}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<CalendarComponent
mode="single"
selected={scheduledDate}
onSelect={setScheduledDate}
/>
</PopoverContent>
</Popover>
</div>
<div>
<Label className="text-sm text-slate-600">Time</Label>
<Select value={scheduledTime} onValueChange={setScheduledTime}>
<SelectTrigger className="mt-1.5">
<Clock className="w-4 h-4 mr-2" />
<SelectValue />
</SelectTrigger>
<SelectContent>
{['09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00'].map(time => (
<SelectItem key={time} value={time}>{time}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
{/* LinkedIn Preview */}
<Card className="border-0 shadow-lg shadow-slate-200/50 overflow-hidden">
<CardHeader className="pb-3 bg-gradient-to-r from-[#0A66C2] to-[#004182]">
<CardTitle className="text-base font-semibold text-white flex items-center gap-2">
<Linkedin className="w-4 h-4" />
LinkedIn Preview
</CardTitle>
</CardHeader>
<CardContent className="p-0">
<div className="p-4 bg-white">
{/* Post Header */}
<div className="flex items-start gap-3">
<Avatar className="h-12 w-12">
<AvatarImage src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop" />
<AvatarFallback>AB</AvatarFallback>
</Avatar>
<div className="flex-1">
<p className="font-semibold text-sm text-slate-900">Alex Business</p>
<p className="text-xs text-slate-500">Marketing Director at TechCorp</p>
<p className="text-xs text-slate-400 flex items-center gap-1 mt-0.5">
{format(scheduledDate, 'MMM d')} • <Globe className="w-3 h-3" />
</p>
</div>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="w-4 h-4" />
</Button>
</div>
{/* Post Content */}
<div className="mt-3 text-sm text-slate-800 whitespace-pre-line leading-relaxed">
{content.length > 300 ? content.slice(0, 300) + '...' : content}
{content.length > 300 && (
<button className="text-blue-600 hover:underline ml-1">see more</button>
)}
</div>
{/* Post Image Preview */}
{postType !== 'content_only' && (
<div className="mt-3 -mx-4">
<div className="aspect-video bg-gradient-to-br from-blue-100 to-indigo-100 flex items-center justify-center">
{postType === 'carousel' ? (
<div className="text-center">
<Layers className="w-12 h-12 text-blue-400 mx-auto mb-2" />
<p className="text-sm text-blue-600">{carouselSlides.length} slides</p>
</div>
) : (
<Image className="w-12 h-12 text-blue-400" />
)}
</div>
</div>
)}
{/* Engagement Stats */}
<div className="flex items-center justify-between mt-3 pt-3 border-t border-slate-100">
<div className="flex items-center gap-1 text-xs text-slate-500">
<div className="flex -space-x-1">
<div className="w-4 h-4 rounded-full bg-blue-500 flex items-center justify-center">
<ThumbsUp className="w-2 h-2 text-white" />
</div>
<div className="w-4 h-4 rounded-full bg-red-500 flex items-center justify-center">
<span className="text-[8px]">❤️</span>
</div>
</div>
<span>Preview mode</span>
</div>
<span className="text-xs text-slate-500">0 comments</span>
</div>
{/* Action Buttons */}
<div className="flex items-center justify-between mt-3 pt-3 border-t border-slate-100">
{[
{ icon: ThumbsUp, label: 'Like' },
{ icon: MessageCircle, label: 'Comment' },
{ icon: Share2, label: 'Share' },
{ icon: Send, label: 'Send' },
].map((action, idx) => (
<button
key={idx}
className="flex items-center gap-1.5 px-3 py-2 text-slate-600 hover:bg-slate-50 rounded-lg transition-colors"
>
<action.icon className="w-4 h-4" />
<span className="text-xs font-medium">{action.label}</span>
</button>
))}
</div>
</div>
</CardContent>
</Card>
{/* Action Buttons */}
<div className="space-y-3">
<Button className="w-full gap-2 h-12 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 shadow-lg shadow-blue-500/25">
<Send className="w-5 h-5" />
Schedule for {format(scheduledDate, 'MMM d')} at {scheduledTime}
</Button>
<Button variant="outline" className="w-full gap-2 h-12 border-slate-200">
<Eye className="w-5 h-5" />
Post Immediately
</Button>
</div>
</motion.div>
</div>
</div>
</div>
);
}