Download frontend/src/lib/mergeSequenceIntoContacts.js from Seth0330/qualifier: direct link, hf CLI and curl.
- Browser
- Download file 2.93 kB
-
https://huggingface.co/spaces/Seth0330/qualifier/resolve/main/frontend/src/lib/mergeSequenceIntoContacts.js
- Command line
-
hf download hf://spaces/Seth0330/qualifier/frontend/src/lib/mergeSequenceIntoContacts.js
-
curl -L -o mergeSequenceIntoContacts.js https://huggingface.co/spaces/Seth0330/qualifier/resolve/main/frontend/src/lib/mergeSequenceIntoContacts.js
2.93 kB
| /** | |
| * Merge one generated sequence row (email or LinkedIn) into grouped contacts for previews. | |
| * When stepOrder is present (campaign wizard), dedupe by stepOrder + channel so merged timelines stay stable. | |
| * Always returns new references (immutable) so React re-renders when LinkedIn rows stream in after emails. | |
| */ | |
| export function mergeSequenceIntoContacts(prev, sequence) { | |
| const ch = sequence.channel || 'email'; | |
| // `id` (the CSV row's sequence_id) is a stable per-contact identifier that's always present | |
| // and unique within a run — prefer it so contacts without an email (common for Apollo rows | |
| // with only a LinkedIn URL) never collide with another no-email contact sharing the same name. | |
| const matchIdx = | |
| sequence.id != null | |
| ? prev.findIndex((c) => c.id === sequence.id) | |
| : prev.findIndex( | |
| (c) => | |
| c.firstName === sequence.firstName && | |
| c.lastName === sequence.lastName && | |
| c.email === sequence.email | |
| ); | |
| const stepOrder = | |
| sequence.stepOrder != null && sequence.stepOrder !== undefined ? sequence.stepOrder : null; | |
| const step = { | |
| emailNumber: sequence.emailNumber || 1, | |
| subject: sequence.subject, | |
| emailContent: sequence.emailContent, | |
| channel: ch, | |
| stepOrder, | |
| }; | |
| const dedupeKey = | |
| stepOrder != null ? `ord-${stepOrder}-${ch}` : `${ch}-${step.emailNumber}`; | |
| if (matchIdx === -1) { | |
| const base = { | |
| id: sequence.id, | |
| firstName: sequence.firstName, | |
| lastName: sequence.lastName, | |
| email: sequence.email, | |
| company: sequence.company, | |
| title: sequence.title, | |
| product: sequence.product, | |
| emails: [], | |
| linkedin: [], | |
| }; | |
| if (ch === 'linkedin') { | |
| base.linkedin = [step]; | |
| } else { | |
| base.emails = [step]; | |
| } | |
| return [...prev, base]; | |
| } | |
| const existing = prev[matchIdx]; | |
| const emails = [...(existing.emails || [])]; | |
| const linkedin = [...(existing.linkedin || [])]; | |
| if (ch === 'linkedin') { | |
| if ( | |
| !linkedin.some((e) => { | |
| const k = | |
| e.stepOrder != null ? `ord-${e.stepOrder}-${ch}` : `${ch}-${e.emailNumber}`; | |
| return k === dedupeKey; | |
| }) | |
| ) { | |
| linkedin.push(step); | |
| } | |
| } else { | |
| if ( | |
| !emails.some((e) => { | |
| const k = | |
| e.stepOrder != null ? `ord-${e.stepOrder}-${ch}` : `${ch}-${e.emailNumber}`; | |
| return k === dedupeKey; | |
| }) | |
| ) { | |
| emails.push(step); | |
| } | |
| } | |
| const updated = { | |
| ...existing, | |
| emails, | |
| linkedin, | |
| }; | |
| return prev.map((c, i) => (i === matchIdx ? updated : c)); | |
| } | |