Seth commited on
Commit
5df542b
·
1 Parent(s): f485041
frontend/src/components/prompts/PromptEditor.jsx CHANGED
@@ -460,6 +460,26 @@ export default function PromptEditor({ selectedProducts, prompts, onPromptsChang
460
  border-slate-200 focus:border-violet-300 focus:ring-violet-200"
461
  />
462
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  </motion.div>
464
  </TabsContent>
465
  ))}
 
460
  border-slate-200 focus:border-violet-300 focus:ring-violet-200"
461
  />
462
  </div>
463
+ {/* Save button at bottom for easy access after scrolling */}
464
+ <div className="border-t border-slate-100 bg-slate-50/50 px-6 py-4 flex justify-end">
465
+ <Button
466
+ size="sm"
467
+ onClick={() => handleSave(product.name)}
468
+ className="bg-violet-600 hover:bg-violet-700"
469
+ >
470
+ {savedStatus[product.name] ? (
471
+ <>
472
+ <CheckCircle2 className="h-4 w-4 mr-1" />
473
+ Saved!
474
+ </>
475
+ ) : (
476
+ <>
477
+ <Save className="h-4 w-4 mr-1" />
478
+ Save Template
479
+ </>
480
+ )}
481
+ </Button>
482
+ </div>
483
  </motion.div>
484
  </TabsContent>
485
  ))}
frontend/src/components/sequences/SequenceViewer.jsx CHANGED
@@ -14,6 +14,7 @@ export default function SequenceViewer({ isGenerating, contactCount, selectedPro
14
  const [searchQuery, setSearchQuery] = useState('');
15
  const [filterProduct, setFilterProduct] = useState('all');
16
  const [isComplete, setIsComplete] = useState(false);
 
17
 
18
  useEffect(() => {
19
  if (isGenerating && uploadedFile?.fileId) {
@@ -140,6 +141,19 @@ export default function SequenceViewer({ isGenerating, contactCount, selectedPro
140
  return matchesSearch && matchesFilter;
141
  });
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  return (
144
  <div className="w-full">
145
  {/* Progress Header */}
@@ -164,15 +178,15 @@ export default function SequenceViewer({ isGenerating, contactCount, selectedPro
164
  </p>
165
  </div>
166
  </div>
167
- {isComplete && (
168
- <Button
169
- onClick={handleDownload}
170
- className="bg-green-600 hover:bg-green-700"
171
- >
172
- <Download className="h-4 w-4 mr-2" />
173
- Download CSV for Klenty
174
- </Button>
175
- )}
176
  </div>
177
  <Progress value={progress} className="h-2" />
178
  </div>
@@ -210,13 +224,29 @@ export default function SequenceViewer({ isGenerating, contactCount, selectedPro
210
  </motion.div>
211
  )}
212
 
213
- {/* Sequence List */}
214
  <div className="space-y-3 max-h-[600px] overflow-y-auto pr-2 custom-scrollbar">
215
  <AnimatePresence>
216
- {filteredContacts.map((contact, index) => (
217
  <SequenceCard key={contact.id || `${contact.firstName}-${contact.lastName}-${contact.email}`} contact={contact} index={index} />
218
  ))}
219
  </AnimatePresence>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  </div>
221
 
222
  {/* Empty State */}
 
14
  const [searchQuery, setSearchQuery] = useState('');
15
  const [filterProduct, setFilterProduct] = useState('all');
16
  const [isComplete, setIsComplete] = useState(false);
17
+ const [displayedCount, setDisplayedCount] = useState(50); // Pagination: show 50 at a time
18
 
19
  useEffect(() => {
20
  if (isGenerating && uploadedFile?.fileId) {
 
141
  return matchesSearch && matchesFilter;
142
  });
143
 
144
+ // Reset pagination when search/filter changes
145
+ useEffect(() => {
146
+ setDisplayedCount(50);
147
+ }, [searchQuery, filterProduct]);
148
+
149
+ // Pagination: only show first N contacts to avoid browser performance issues
150
+ const displayedContacts = filteredContacts.slice(0, displayedCount);
151
+ const hasMore = filteredContacts.length > displayedCount;
152
+
153
+ const loadMore = () => {
154
+ setDisplayedCount(prev => Math.min(prev + 50, filteredContacts.length));
155
+ };
156
+
157
  return (
158
  <div className="w-full">
159
  {/* Progress Header */}
 
178
  </p>
179
  </div>
180
  </div>
181
+ {isComplete && (
182
+ <Button
183
+ onClick={handleDownload}
184
+ className="bg-green-600 hover:bg-green-700"
185
+ >
186
+ <Download className="h-4 w-4 mr-2" />
187
+ Download CSV for Outreaches
188
+ </Button>
189
+ )}
190
  </div>
191
  <Progress value={progress} className="h-2" />
192
  </div>
 
224
  </motion.div>
225
  )}
226
 
227
+ {/* Sequence List - Optimized for high volume with pagination */}
228
  <div className="space-y-3 max-h-[600px] overflow-y-auto pr-2 custom-scrollbar">
229
  <AnimatePresence>
230
+ {displayedContacts.map((contact, index) => (
231
  <SequenceCard key={contact.id || `${contact.firstName}-${contact.lastName}-${contact.email}`} contact={contact} index={index} />
232
  ))}
233
  </AnimatePresence>
234
+ {hasMore && (
235
+ <div className="text-center py-4">
236
+ <Button
237
+ variant="outline"
238
+ onClick={loadMore}
239
+ className="mx-auto"
240
+ >
241
+ Load More ({filteredContacts.length - displayedCount} remaining)
242
+ </Button>
243
+ </div>
244
+ )}
245
+ {filteredContacts.length > 0 && (
246
+ <div className="text-center py-2 text-sm text-slate-500">
247
+ Showing {displayedContacts.length} of {filteredContacts.length} contacts
248
+ </div>
249
+ )}
250
  </div>
251
 
252
  {/* Empty State */}
frontend/src/pages/EmailSequenceGenerator.jsx CHANGED
@@ -7,7 +7,6 @@ import UploadStep from '@/components/upload/UploadStep';
7
  import ProductSelector from '@/components/products/ProductSelector';
8
  import PromptEditor from '@/components/prompts/PromptEditor';
9
  import SequenceViewer from '@/components/sequences/SequenceViewer';
10
- import SmartleadPanel from '@/components/smartlead/SmartleadPanel';
11
 
12
  export default function EmailSequenceGenerator() {
13
  const [step, setStep] = useState(1);
@@ -255,25 +254,8 @@ export default function EmailSequenceGenerator() {
255
  onComplete={handleGenerationComplete}
256
  />
257
 
258
- {generationComplete && (
259
- <motion.div
260
- initial={{ opacity: 0, y: 20 }}
261
- animate={{ opacity: 1, y: 0 }}
262
- transition={{ delay: 0.3 }}
263
- className="mt-8"
264
- >
265
- <SmartleadPanel
266
- uploadedFile={uploadedFile}
267
- sequencesCount={uploadedFile?.contactCount || 0}
268
- onPushComplete={(result) => {
269
- console.log('Push completed:', result);
270
- }}
271
- />
272
- </motion.div>
273
- )}
274
-
275
  {!isGenerating && (
276
- <div className="flex justify-between pt-4">
277
  <Button
278
  variant="outline"
279
  onClick={() => setStep(2)}
@@ -282,13 +264,6 @@ export default function EmailSequenceGenerator() {
282
  <ArrowLeft className="h-4 w-4 mr-2" />
283
  Edit Templates
284
  </Button>
285
- <Button
286
- variant="outline"
287
- onClick={() => window.location.href = '/history'}
288
- className="px-6"
289
- >
290
- View Run History
291
- </Button>
292
  </div>
293
  )}
294
  </motion.div>
@@ -300,7 +275,7 @@ export default function EmailSequenceGenerator() {
300
  <footer className="border-t border-slate-100 mt-16">
301
  <div className="max-w-6xl mx-auto px-6 py-6">
302
  <p className="text-center text-sm text-slate-400">
303
- Powered by AI • Export ready for Klenty, Outreach, and more
304
  </p>
305
  </div>
306
  </footer>
 
7
  import ProductSelector from '@/components/products/ProductSelector';
8
  import PromptEditor from '@/components/prompts/PromptEditor';
9
  import SequenceViewer from '@/components/sequences/SequenceViewer';
 
10
 
11
  export default function EmailSequenceGenerator() {
12
  const [step, setStep] = useState(1);
 
254
  onComplete={handleGenerationComplete}
255
  />
256
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  {!isGenerating && (
258
+ <div className="flex justify-start pt-4">
259
  <Button
260
  variant="outline"
261
  onClick={() => setStep(2)}
 
264
  <ArrowLeft className="h-4 w-4 mr-2" />
265
  Edit Templates
266
  </Button>
 
 
 
 
 
 
 
267
  </div>
268
  )}
269
  </motion.div>
 
275
  <footer className="border-t border-slate-100 mt-16">
276
  <div className="max-w-6xl mx-auto px-6 py-6">
277
  <p className="text-center text-sm text-slate-400">
278
+ Powered by AI • Export ready for Outreaches, Smartlead, and more
279
  </p>
280
  </div>
281
  </footer>