| 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> |
| ); |
| } |
|
|