EMAILOUT / frontend /src /lib /mergeSequenceIntoContacts.js
Seth
update
c85593f
/**
* 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';
const matchIdx = 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));
}