Spaces:
Sleeping
Sleeping
| 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, | |
| } 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> | |
| ); | |
| } | |