| | import React, { useState, useRef } from 'react'; |
| | import { Sparkles, ArrowRight, ArrowLeft, Mail, Zap } from 'lucide-react'; |
| | import { Button } from "@/components/ui/button"; |
| | import { motion, AnimatePresence } from 'framer-motion'; |
| |
|
| | import UploadStep from '@/components/upload/UploadStep'; |
| | import ProductSelector from '@/components/products/ProductSelector'; |
| | import PromptEditor from '@/components/prompts/PromptEditor'; |
| | import SequenceViewer from '@/components/sequences/SequenceViewer'; |
| |
|
| | export default function EmailSequenceGenerator() { |
| | const [step, setStep] = useState(1); |
| | const [uploadedFile, setUploadedFile] = useState(null); |
| | const [selectedProducts, setSelectedProducts] = useState([]); |
| | const [prompts, setPrompts] = useState({}); |
| | const [isGenerating, setIsGenerating] = useState(false); |
| | const [generationComplete, setGenerationComplete] = useState(false); |
| | const [generationRunId, setGenerationRunId] = useState(0); |
| | const generateButtonRef = useRef(null); |
| |
|
| | const canProceedToStep2 = uploadedFile && selectedProducts.length > 0; |
| | const canProceedToStep3 = Object.keys(prompts).length > 0; |
| |
|
| | const scrollToGenerateButton = () => { |
| | if (generateButtonRef.current) { |
| | generateButtonRef.current.scrollIntoView({ |
| | behavior: 'smooth', |
| | block: 'center' |
| | }); |
| | } |
| | }; |
| |
|
| | const handleGenerate = async () => { |
| | if (!uploadedFile?.fileId || selectedProducts.length === 0 || Object.keys(prompts).length === 0) { |
| | alert('Please complete all steps before generating sequences.'); |
| | return; |
| | } |
| |
|
| | |
| | try { |
| | const res = await fetch('/api/save-prompts', { |
| | method: 'POST', |
| | headers: { 'Content-Type': 'application/json' }, |
| | body: JSON.stringify({ |
| | file_id: uploadedFile.fileId, |
| | prompts: prompts, |
| | products: selectedProducts.map(p => p.name) |
| | }) |
| | }); |
| | if (!res.ok) { |
| | const err = await res.json().catch(() => ({})); |
| | throw new Error(err.detail || res.statusText); |
| | } |
| | } catch (error) { |
| | console.error('Error saving prompts:', error); |
| | alert('Failed to save templates. Please try again.'); |
| | return; |
| | } |
| |
|
| | setGenerationRunId((r) => r + 1); |
| | setStep(3); |
| | setIsGenerating(true); |
| | }; |
| |
|
| | const handleGenerationComplete = () => { |
| | setIsGenerating(false); |
| | setGenerationComplete(true); |
| | }; |
| |
|
| | const handleReset = () => { |
| | setStep(1); |
| | setUploadedFile(null); |
| | setSelectedProducts([]); |
| | setPrompts({}); |
| | setIsGenerating(false); |
| | setGenerationComplete(false); |
| | }; |
| |
|
| | return ( |
| | <div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-violet-50"> |
| | {/* Header */} |
| | <header className="border-b border-slate-100 bg-white/80 backdrop-blur-sm sticky top-0 z-50"> |
| | <div className="max-w-6xl mx-auto px-6 py-4"> |
| | <div className="flex items-center justify-between"> |
| | <div className="flex items-center gap-3"> |
| | <div className="h-10 w-10 rounded-xl bg-gradient-to-br from-violet-600 to-purple-600 |
| | flex items-center justify-center shadow-lg shadow-violet-200"> |
| | <Zap className="h-5 w-5 text-white" /> |
| | </div> |
| | <div> |
| | <h1 className="font-bold text-slate-800 text-lg">SequenceAI</h1> |
| | <p className="text-xs text-slate-500">Personalized Email Outreach</p> |
| | </div> |
| | </div> |
| | {step > 1 && ( |
| | <Button |
| | variant="ghost" |
| | onClick={handleReset} |
| | className="text-slate-500 hover:text-slate-700" |
| | > |
| | Start Over |
| | </Button> |
| | )} |
| | </div> |
| | </div> |
| | </header> |
| | |
| | <main className="max-w-6xl mx-auto px-6 py-8"> |
| | {/* Progress Steps */} |
| | <div className="mb-10"> |
| | <div className="flex items-center justify-center gap-4"> |
| | {[ |
| | { num: 1, label: 'Upload & Select' }, |
| | { num: 2, label: 'Configure Prompts' }, |
| | { num: 3, label: 'Generate & Export' } |
| | ].map((s, idx) => ( |
| | <React.Fragment key={s.num}> |
| | <div className="flex items-center gap-2"> |
| | <div className={` |
| | h-8 w-8 rounded-full flex items-center justify-center text-sm font-semibold |
| | transition-all duration-300 |
| | ${step >= s.num |
| | ? 'bg-violet-600 text-white shadow-lg shadow-violet-200' |
| | : 'bg-slate-100 text-slate-400' |
| | } |
| | `}> |
| | {s.num} |
| | </div> |
| | <span className={`text-sm font-medium hidden sm:block ${ |
| | step >= s.num ? 'text-slate-800' : 'text-slate-400' |
| | }`}> |
| | {s.label} |
| | </span> |
| | </div> |
| | {idx < 2 && ( |
| | <div className={`h-0.5 w-12 rounded-full transition-colors duration-300 ${ |
| | step > s.num ? 'bg-violet-600' : 'bg-slate-200' |
| | }`} /> |
| | )} |
| | </React.Fragment> |
| | ))} |
| | </div> |
| | </div> |
| | |
| | <AnimatePresence mode="wait"> |
| | {/* Step 1: Upload & Product Selection */} |
| | {step === 1 && ( |
| | <motion.div |
| | key="step1" |
| | initial={{ opacity: 0, x: -20 }} |
| | animate={{ opacity: 1, x: 0 }} |
| | exit={{ opacity: 0, x: 20 }} |
| | transition={{ duration: 0.3 }} |
| | className="space-y-8" |
| | > |
| | <div className="text-center mb-8"> |
| | <h2 className="text-2xl font-bold text-slate-800 mb-2"> |
| | Upload Your Contacts |
| | </h2> |
| | <p className="text-slate-500"> |
| | Import your Apollo CSV and select the products for your outreach campaign |
| | </p> |
| | </div> |
| | |
| | <UploadStep |
| | onFileUploaded={setUploadedFile} |
| | uploadedFile={uploadedFile} |
| | onRemoveFile={() => setUploadedFile(null)} |
| | /> |
| | |
| | {uploadedFile && ( |
| | <motion.div |
| | initial={{ opacity: 0, y: 20 }} |
| | animate={{ opacity: 1, y: 0 }} |
| | transition={{ delay: 0.2 }} |
| | > |
| | <ProductSelector |
| | selectedProducts={selectedProducts} |
| | onProductsChange={setSelectedProducts} |
| | /> |
| | </motion.div> |
| | )} |
| | |
| | <div className="flex justify-end pt-4"> |
| | <Button |
| | onClick={() => setStep(2)} |
| | disabled={!canProceedToStep2} |
| | className="bg-violet-600 hover:bg-violet-700 px-6" |
| | > |
| | Continue to Prompts |
| | <ArrowRight className="h-4 w-4 ml-2" /> |
| | </Button> |
| | </div> |
| | </motion.div> |
| | )} |
| | |
| | {/* Step 2: Prompt Configuration */} |
| | {step === 2 && ( |
| | <motion.div |
| | key="step2" |
| | initial={{ opacity: 0, x: -20 }} |
| | animate={{ opacity: 1, x: 0 }} |
| | exit={{ opacity: 0, x: 20 }} |
| | transition={{ duration: 0.3 }} |
| | className="space-y-8" |
| | > |
| | <div className="text-center mb-8"> |
| | <h2 className="text-2xl font-bold text-slate-800 mb-2"> |
| | Customize Your Email Templates |
| | </h2> |
| | <p className="text-slate-500"> |
| | Edit the prompt templates for each product. The AI will personalize these for each contact. |
| | </p> |
| | </div> |
| | |
| | <PromptEditor |
| | selectedProducts={selectedProducts} |
| | prompts={prompts} |
| | onPromptsChange={setPrompts} |
| | onSaveComplete={scrollToGenerateButton} |
| | /> |
| | |
| | <div className="flex justify-between pt-4"> |
| | <Button |
| | variant="outline" |
| | onClick={() => setStep(1)} |
| | className="px-6" |
| | > |
| | <ArrowLeft className="h-4 w-4 mr-2" /> |
| | Back |
| | </Button> |
| | <Button |
| | ref={generateButtonRef} |
| | onClick={handleGenerate} |
| | disabled={!canProceedToStep3} |
| | className="bg-gradient-to-r from-violet-600 to-purple-600 hover:from-violet-700 |
| | hover:to-purple-700 px-8 shadow-lg shadow-violet-200" |
| | > |
| | <Sparkles className="h-4 w-4 mr-2" /> |
| | Generate Sequences |
| | </Button> |
| | </div> |
| | </motion.div> |
| | )} |
| | |
| | {/* Step 3: Generation & Results */} |
| | {step === 3 && ( |
| | <motion.div |
| | key="step3" |
| | initial={{ opacity: 0, x: -20 }} |
| | animate={{ opacity: 1, x: 0 }} |
| | exit={{ opacity: 0, x: 20 }} |
| | transition={{ duration: 0.3 }} |
| | className="space-y-8" |
| | > |
| | <div className="text-center mb-8"> |
| | <h2 className="text-2xl font-bold text-slate-800 mb-2"> |
| | {generationComplete ? 'Your Sequences Are Ready!' : 'Generating Personalized Emails'} |
| | </h2> |
| | <p className="text-slate-500"> |
| | {generationComplete |
| | ? 'Review your sequences below and download when ready' |
| | : 'Our AI is crafting personalized emails for each contact' |
| | } |
| | </p> |
| | </div> |
| | |
| | <SequenceViewer |
| | isGenerating={isGenerating} |
| | generationRunId={generationRunId} |
| | contactCount={uploadedFile?.contactCount || 50} |
| | selectedProducts={selectedProducts} |
| | uploadedFile={uploadedFile} |
| | prompts={prompts} |
| | onComplete={handleGenerationComplete} |
| | /> |
| | |
| | {!isGenerating && ( |
| | <div className="flex justify-start pt-4"> |
| | <Button |
| | variant="outline" |
| | onClick={() => setStep(2)} |
| | className="px-6" |
| | > |
| | <ArrowLeft className="h-4 w-4 mr-2" /> |
| | Edit Templates |
| | </Button> |
| | </div> |
| | )} |
| | </motion.div> |
| | )} |
| | </AnimatePresence> |
| | </main> |
| | |
| | {/* Footer */} |
| | <footer className="border-t border-slate-100 mt-16"> |
| | <div className="max-w-6xl mx-auto px-6 py-6"> |
| | <p className="text-center text-sm text-slate-400"> |
| | Powered by AI • Export ready for Outreaches, Smartlead, and more |
| | </p> |
| | </div> |
| | </footer> |
| | |
| | {/* Custom Scrollbar Styles */} |
| | <style>{` |
| | .custom-scrollbar::-webkit-scrollbar { |
| | width: 6px; |
| | } |
| | .custom-scrollbar::-webkit-scrollbar-track { |
| | background: #f1f5f9; |
| | border-radius: 3px; |
| | } |
| | .custom-scrollbar::-webkit-scrollbar-thumb { |
| | background: #cbd5e1; |
| | border-radius: 3px; |
| | } |
| | .custom-scrollbar::-webkit-scrollbar-thumb:hover { |
| | background: #94a3b8; |
| | } |
| | `}</style> |
| | </div> |
| | ); |
| | } |
| |
|