Seth commited on
Commit
42babfe
·
1 Parent(s): a13ea2b
frontend/src/components/campaigns/CampaignSequenceBuilder.jsx CHANGED
@@ -11,6 +11,13 @@ import {
11
  X,
12
  } from 'lucide-react';
13
  import { cn } from '@/lib/utils';
 
 
 
 
 
 
 
14
 
15
  function uid() {
16
  return typeof crypto !== 'undefined' && crypto.randomUUID
@@ -196,13 +203,25 @@ function WaitCard({ step, onRemove, onDaysChange }) {
196
  );
197
  }
198
 
199
- function ActionCard({ step, onRemove, senderHint, accountHint, emailAccountHint }) {
 
 
 
 
 
 
 
 
 
200
  const sender = (senderHint || '').trim();
201
  const account = (accountHint || '').trim();
202
  const showSender = sender && (!account || sender !== account);
203
  const showLiMeta = step.channel === 'linkedin' && (showSender || account);
204
  const mailbox = (emailAccountHint || '').trim();
205
  const showEmailMeta = step.channel === 'gmail' && !!mailbox;
 
 
 
206
  return (
207
  <div className="relative w-full max-w-md">
208
  <div className="flex w-full items-center gap-3 rounded-xl border border-slate-200 bg-white px-4 py-3 text-left shadow-sm">
@@ -224,7 +243,35 @@ function ActionCard({ step, onRemove, senderHint, accountHint, emailAccountHint
224
  {account ? <span>Profile: {account}</span> : null}
225
  </p>
226
  ) : null}
227
- {showEmailMeta ? (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  <p className="mt-1 text-[11px] leading-snug text-violet-700">Mailbox: {mailbox}</p>
229
  ) : null}
230
  </div>
@@ -291,13 +338,30 @@ export default function CampaignSequenceBuilder({ value, onChange, linkedinDefau
291
  linkedinDefaults?.default_unipile_account_ref_id ?? null;
292
  newStep.sender_profile_name = linkedinDefaults?.linkedin_profile_display_name || '';
293
  }
 
 
 
 
294
  }
295
  const next = [...steps];
296
  next.splice(index, 0, newStep);
297
  setSteps(next);
298
  closePicker();
299
  },
300
- [steps, setSteps, closePicker, linkedinDefaults]
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  );
302
 
303
  const removeAt = useCallback(
@@ -367,7 +431,21 @@ export default function CampaignSequenceBuilder({ value, onChange, linkedinDefau
367
  />
368
  </div>
369
 
370
- {steps.map((step, index) => (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  <React.Fragment key={step.id}>
372
  {step.type === 'wait' ? (
373
  <WaitCard
@@ -409,6 +487,15 @@ export default function CampaignSequenceBuilder({ value, onChange, linkedinDefau
409
  )
410
  : ''
411
  }
 
 
 
 
 
 
 
 
 
412
  onRemove={() => removeAt(index)}
413
  />
414
  )}
@@ -431,7 +518,8 @@ export default function CampaignSequenceBuilder({ value, onChange, linkedinDefau
431
  />
432
  </div>
433
  </React.Fragment>
434
- ))}
 
435
 
436
  {/* END */}
437
  <div className="mt-1 flex flex-col items-center">
 
11
  X,
12
  } from 'lucide-react';
13
  import { cn } from '@/lib/utils';
14
+ import {
15
+ Select,
16
+ SelectContent,
17
+ SelectItem,
18
+ SelectTrigger,
19
+ SelectValue,
20
+ } from '@/components/ui/select';
21
 
22
  function uid() {
23
  return typeof crypto !== 'undefined' && crypto.randomUUID
 
203
  );
204
  }
205
 
206
+ function ActionCard({
207
+ step,
208
+ onRemove,
209
+ senderHint,
210
+ accountHint,
211
+ emailAccountHint,
212
+ emailMailboxAccounts,
213
+ emailMailboxSelectValue,
214
+ onEmailMailboxChange,
215
+ }) {
216
  const sender = (senderHint || '').trim();
217
  const account = (accountHint || '').trim();
218
  const showSender = sender && (!account || sender !== account);
219
  const showLiMeta = step.channel === 'linkedin' && (showSender || account);
220
  const mailbox = (emailAccountHint || '').trim();
221
  const showEmailMeta = step.channel === 'gmail' && !!mailbox;
222
+ const mailAccounts = emailMailboxAccounts || [];
223
+ const showEmailMailboxSelect =
224
+ step.channel === 'gmail' && mailAccounts.length > 1 && typeof onEmailMailboxChange === 'function';
225
  return (
226
  <div className="relative w-full max-w-md">
227
  <div className="flex w-full items-center gap-3 rounded-xl border border-slate-200 bg-white px-4 py-3 text-left shadow-sm">
 
243
  {account ? <span>Profile: {account}</span> : null}
244
  </p>
245
  ) : null}
246
+ {showEmailMailboxSelect ? (
247
+ <div
248
+ className="mt-2 space-y-1"
249
+ onClick={(e) => e.stopPropagation()}
250
+ onPointerDown={(e) => e.stopPropagation()}
251
+ >
252
+ <p className="text-[10px] font-medium uppercase tracking-wide text-slate-500">
253
+ Mailbox
254
+ </p>
255
+ <Select
256
+ value={String(emailMailboxSelectValue ?? mailAccounts[0]?.id ?? '')}
257
+ onValueChange={(v) => onEmailMailboxChange(Number(v))}
258
+ >
259
+ <SelectTrigger className="h-9 w-full max-w-[240px] border-slate-200 text-xs">
260
+ <SelectValue placeholder="Choose mailbox" />
261
+ </SelectTrigger>
262
+ <SelectContent>
263
+ {mailAccounts.map((a) => {
264
+ const label = (a.display_name || a.label || 'Mailbox').trim();
265
+ return (
266
+ <SelectItem key={a.id} value={String(a.id)}>
267
+ {label}
268
+ </SelectItem>
269
+ );
270
+ })}
271
+ </SelectContent>
272
+ </Select>
273
+ </div>
274
+ ) : showEmailMeta ? (
275
  <p className="mt-1 text-[11px] leading-snug text-violet-700">Mailbox: {mailbox}</p>
276
  ) : null}
277
  </div>
 
338
  linkedinDefaults?.default_unipile_account_ref_id ?? null;
339
  newStep.sender_profile_name = linkedinDefaults?.linkedin_profile_display_name || '';
340
  }
341
+ if (preset.channel === 'gmail') {
342
+ newStep.unipile_account_ref_id =
343
+ mailboxDefaults?.default_mailbox_unipile_account_ref_id ?? null;
344
+ }
345
  }
346
  const next = [...steps];
347
  next.splice(index, 0, newStep);
348
  setSteps(next);
349
  closePicker();
350
  },
351
+ [steps, setSteps, closePicker, linkedinDefaults, mailboxDefaults]
352
+ );
353
+
354
+ const updateStepMailbox = useCallback(
355
+ (index, refId) => {
356
+ setSteps((prev) =>
357
+ prev.map((s, i) =>
358
+ i === index && s.type === 'action' && s.channel === 'gmail'
359
+ ? { ...s, unipile_account_ref_id: refId }
360
+ : s
361
+ )
362
+ );
363
+ },
364
+ [setSteps]
365
  );
366
 
367
  const removeAt = useCallback(
 
431
  />
432
  </div>
433
 
434
+ {steps.map((step, index) => {
435
+ const mailAccts = mailboxDefaults?.accounts || [];
436
+ const defaultMailRef = mailboxDefaults?.default_mailbox_unipile_account_ref_id ?? null;
437
+ const effMailRef =
438
+ step.type === 'action' && step.channel === 'gmail'
439
+ ? step.unipile_account_ref_id ?? defaultMailRef
440
+ : null;
441
+ const mailIdSet = new Set(mailAccts.map((a) => Number(a.id)));
442
+ const emailSelectValue =
443
+ effMailRef != null && mailIdSet.has(Number(effMailRef))
444
+ ? Number(effMailRef)
445
+ : mailAccts[0]?.id != null
446
+ ? Number(mailAccts[0].id)
447
+ : null;
448
+ return (
449
  <React.Fragment key={step.id}>
450
  {step.type === 'wait' ? (
451
  <WaitCard
 
487
  )
488
  : ''
489
  }
490
+ emailMailboxAccounts={step.channel === 'gmail' ? mailAccts : []}
491
+ emailMailboxSelectValue={
492
+ step.channel === 'gmail' ? emailSelectValue : null
493
+ }
494
+ onEmailMailboxChange={
495
+ step.channel === 'gmail' && mailAccts.length > 1
496
+ ? (id) => updateStepMailbox(index, id)
497
+ : undefined
498
+ }
499
  onRemove={() => removeAt(index)}
500
  />
501
  )}
 
518
  />
519
  </div>
520
  </React.Fragment>
521
+ );
522
+ })}
523
 
524
  {/* END */}
525
  <div className="mt-1 flex flex-col items-center">
frontend/src/components/campaigns/CreateCampaignWizard.jsx CHANGED
@@ -54,7 +54,16 @@ function estimateCsvRows(file) {
54
  });
55
  }
56
 
57
- function buildCampaignSequenceAppendix(steps) {
 
 
 
 
 
 
 
 
 
58
  const lines = [
59
  WIZARD_SEQUENCE_TAG,
60
  '(AUTHORITATIVE — OVERRIDES ANY FIXED “N EMAILS” OR “N MESSAGES” COUNT IN THIS PROMPT)',
@@ -71,7 +80,10 @@ function buildCampaignSequenceAppendix(steps) {
71
  if (s.type === 'action') {
72
  n += 1;
73
  if (s.channel === 'gmail') {
74
- lines.push(`${n}. Gmail ${s.title || 'Email'}`);
 
 
 
75
  } else if (s.channel === 'linkedin') {
76
  const kind =
77
  s.action === 'linkedin_connect'
@@ -542,9 +554,27 @@ export default function CreateCampaignWizard({
542
  );
543
  }, [linkedinDefaults]);
544
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545
  useEffect(() => {
546
  if (step !== 3 || selectedProducts.length === 0) return;
547
- const appendix = buildCampaignSequenceAppendix(sequenceSteps);
548
  setPrompts((prev) => {
549
  const next = { ...prev };
550
  let changed = false;
@@ -576,7 +606,7 @@ export default function CreateCampaignWizard({
576
  });
577
  return changed ? next : prev;
578
  });
579
- }, [step, sequenceSteps, selectedProducts, sequenceHasLinkedin]);
580
 
581
  useEffect(() => {
582
  if (!genRunning || !wizardUpload?.fileId) return;
 
54
  });
55
  }
56
 
57
+ function mailboxLabelForAppendixStep(step, mailboxDefaults) {
58
+ if (step.channel !== 'gmail' || !mailboxDefaults) return '';
59
+ const ref =
60
+ step.unipile_account_ref_id ?? mailboxDefaults.default_mailbox_unipile_account_ref_id ?? null;
61
+ if (ref == null) return '';
62
+ const a = (mailboxDefaults.accounts || []).find((x) => Number(x.id) === Number(ref));
63
+ return ((a?.display_name || a?.label || '') || '').trim();
64
+ }
65
+
66
+ function buildCampaignSequenceAppendix(steps, mailboxDefaults = null) {
67
  const lines = [
68
  WIZARD_SEQUENCE_TAG,
69
  '(AUTHORITATIVE — OVERRIDES ANY FIXED “N EMAILS” OR “N MESSAGES” COUNT IN THIS PROMPT)',
 
80
  if (s.type === 'action') {
81
  n += 1;
82
  if (s.channel === 'gmail') {
83
+ const mb = mailboxLabelForAppendixStep(s, mailboxDefaults);
84
+ lines.push(
85
+ `${n}. Gmail — ${s.title || 'Email'}${mb ? ` (sending mailbox: ${mb})` : ''}`
86
+ );
87
  } else if (s.channel === 'linkedin') {
88
  const kind =
89
  s.action === 'linkedin_connect'
 
554
  );
555
  }, [linkedinDefaults]);
556
 
557
+ useEffect(() => {
558
+ if (!mailboxDefaults) return;
559
+ const accounts = mailboxDefaults.accounts || [];
560
+ const fallbackId = accounts[0]?.id != null ? Number(accounts[0].id) : null;
561
+ setSequenceSteps((prev) =>
562
+ prev.map((s) => {
563
+ if (s.type !== 'action' || s.channel !== 'gmail') return s;
564
+ const ref =
565
+ s.unipile_account_ref_id ??
566
+ (mailboxDefaults.default_mailbox_unipile_account_ref_id != null
567
+ ? Number(mailboxDefaults.default_mailbox_unipile_account_ref_id)
568
+ : null) ??
569
+ fallbackId;
570
+ return { ...s, unipile_account_ref_id: ref };
571
+ })
572
+ );
573
+ }, [mailboxDefaults]);
574
+
575
  useEffect(() => {
576
  if (step !== 3 || selectedProducts.length === 0) return;
577
+ const appendix = buildCampaignSequenceAppendix(sequenceSteps, mailboxDefaults);
578
  setPrompts((prev) => {
579
  const next = { ...prev };
580
  let changed = false;
 
606
  });
607
  return changed ? next : prev;
608
  });
609
+ }, [step, sequenceSteps, selectedProducts, sequenceHasLinkedin, mailboxDefaults]);
610
 
611
  useEffect(() => {
612
  if (!genRunning || !wizardUpload?.fileId) return;