Skip to content

Commit f982660

Browse files
adambaritoclaude
andauthored
fix: resolve email broadcast Save Draft silent failure (#567)
* fix: resolve email broadcast "Save Draft" silent failure Remove replyTo from createEmailBroadcastWithTemplateSchema — empty string default was failing z.email() validation silently. The backend already pulls replyTo from the EmailAddress record when sending. Also fix layout children type (ReactElement → ReactNode) and scope fan unique index to workspaceId. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: align fan upsert conflict targets with updated unique index The Fans table unique index changed from (email) to (email, workspaceId), but two routes still used the old target, causing runtime errors on repeat fan sign-ups. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7295624 commit f982660

File tree

9 files changed

+6
-11
lines changed

9 files changed

+6
-11
lines changed

apps/app/src/app/[handle]/bios/(edit)/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AppBioRender } from '~/app/[handle]/bios/_components/app-bio-render';
22
import { BioKeySwitcher } from '~/app/[handle]/bios/_components/bio-key-switcher';
33
import { getAllFontClassNames } from '~/lib/fonts';
44

5-
export default function BioEditLayout({ children }: { children: React.ReactElement }) {
5+
export default function BioEditLayout({ children }: { children: React.ReactNode }) {
66
// Load all fonts for bio editing pages where users can preview different fonts
77
const allFontClasses = getAllFontClassNames();
88

apps/app/src/app/[handle]/email/broadcasts/_components/create-or-update-email-broadcast-modal.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ export function CreateOrUpdateEmailBroadcastModal({
167167
previewText: '',
168168
body: '',
169169
type: 'marketing',
170-
replyTo: '',
171170
fanGroupId: null,
172171
status: 'draft',
173172
scheduledAt: null,

apps/app/src/app/[handle]/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default async function HandleLayout({
1212
children,
1313
}: {
1414
params: Promise<{ handle: string }>;
15-
children: React.ReactElement;
15+
children: React.ReactNode;
1616
}) {
1717
const session = await getSession();
1818

packages/db/src/sql/fan.sql.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const Fans = pgTable(
4444
stripePaymentMethodId: varchar('stripePaymentMethodId', { length: 255 }),
4545
},
4646
f => ({
47-
unique: uniqueIndex('unique_email').on(f.email),
47+
unique: uniqueIndex('unique_email').on(f.email, f.workspaceId),
4848
}),
4949
);
5050

packages/lib/src/trpc/routes/bio-render.route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ export const bioRenderRoute = {
236236
updatedAt: new Date(),
237237
})
238238
.onConflictDoUpdate({
239-
target: [Fans.email],
239+
target: [Fans.email, Fans.workspaceId],
240240
set: {
241241
updatedAt: new Date(),
242242
emailMarketingOptIn: marketingConsent,

packages/lib/src/trpc/routes/email-broadcast.route.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@ export const emailBroadcastRoute = {
183183
previewText,
184184
body,
185185
type,
186-
replyTo,
187186
broadcastOnly,
188187
// Broadcast fields
189188
fanGroupId,
@@ -204,7 +203,6 @@ export const emailBroadcastRoute = {
204203
previewText: previewText ?? null,
205204
body,
206205
type,
207-
replyTo: replyTo ?? null,
208206
broadcastOnly, // Hide from templates list if it's a one-off
209207
})
210208
.returning()

packages/lib/src/trpc/routes/vip-swap-render.route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export const vipSwapRenderRoute = {
168168
updatedAt: new Date(),
169169
})
170170
.onConflictDoUpdate({
171-
target: [Fans.email], // Only email is unique, not email+workspace
171+
target: [Fans.email, Fans.workspaceId], // Unique on email+workspace
172172
set: {
173173
updatedAt: new Date(),
174174
emailMarketingOptIn: true,

packages/validators/src/schemas/email-broadcast.schema.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ export const createEmailBroadcastWithTemplateSchema = z.object({
6161
previewText: z.string().optional(),
6262
body: z.string(),
6363
type: z.enum(['marketing', 'transactional']).default('marketing'),
64-
replyTo: z.email().optional(),
65-
6664
// Broadcast fields
6765
fanGroupId: z.string().nullable(),
6866
status: z.enum(['draft', 'scheduled']).default('draft'),

pnpm-lock.yaml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)