131 lines
4.3 KiB
TypeScript
131 lines
4.3 KiB
TypeScript
import { createServerFn } from "@tanstack/react-start";
|
|
import { eq } from "drizzle-orm";
|
|
import { z } from "zod";
|
|
import { user, userOnboardingAnswers } from "@/db/schema";
|
|
import { db } from "@/db";
|
|
import { requireAuthenticatedContext } from "@/serverFunctions/middleware";
|
|
|
|
const onboardingAnswersSchema = z.object({
|
|
interestedFeatures: z.array(z.string()).optional(),
|
|
workFor: z.string().optional(),
|
|
clientWebsiteCount: z.string().optional(),
|
|
foundVia: z.string().optional(),
|
|
mcpSetupIntent: z.enum(["yes", "no"]).optional(),
|
|
completed: z.boolean().optional(),
|
|
});
|
|
|
|
export const getOnboardingAnswers = createServerFn({ method: "GET" })
|
|
.middleware(requireAuthenticatedContext)
|
|
.handler(async ({ context }) => {
|
|
const answers = await db.query.userOnboardingAnswers.findFirst({
|
|
columns: {
|
|
completedAt: true,
|
|
gscNudgeDismissedAt: true,
|
|
interestedFeatures: true,
|
|
workFor: true,
|
|
clientWebsiteCount: true,
|
|
foundVia: true,
|
|
mcpSetupIntent: true,
|
|
},
|
|
where: eq(userOnboardingAnswers.userId, context.userId),
|
|
});
|
|
const userRecord = await db.query.user.findFirst({
|
|
columns: {
|
|
createdAt: true,
|
|
},
|
|
where: eq(user.id, context.userId),
|
|
});
|
|
|
|
let interestedFeatures: string[] = [];
|
|
if (answers?.interestedFeatures) {
|
|
try {
|
|
const parsed: unknown = JSON.parse(answers.interestedFeatures);
|
|
if (Array.isArray(parsed)) {
|
|
interestedFeatures = parsed.filter(
|
|
(value): value is string => typeof value === "string",
|
|
);
|
|
}
|
|
} catch {
|
|
interestedFeatures = [];
|
|
}
|
|
}
|
|
|
|
return {
|
|
completedAt: answers?.completedAt ?? null,
|
|
gscNudgeDismissedAt: answers?.gscNudgeDismissedAt ?? null,
|
|
userCreatedAt: userRecord?.createdAt?.toISOString() ?? null,
|
|
answers: {
|
|
interestedFeatures,
|
|
workFor: answers?.workFor ?? null,
|
|
clientWebsiteCount: answers?.clientWebsiteCount ?? null,
|
|
foundVia: answers?.foundVia ?? null,
|
|
mcpSetupIntent: answers?.mcpSetupIntent ?? null,
|
|
},
|
|
};
|
|
});
|
|
|
|
export const saveOnboardingAnswers = createServerFn({ method: "POST" })
|
|
.middleware(requireAuthenticatedContext)
|
|
.inputValidator((data: unknown) => onboardingAnswersSchema.parse(data))
|
|
.handler(async ({ data, context }) => {
|
|
const now = new Date().toISOString();
|
|
const completedAt = data.completed ? now : undefined;
|
|
const set = {
|
|
...(data.interestedFeatures
|
|
? { interestedFeatures: JSON.stringify(data.interestedFeatures) }
|
|
: {}),
|
|
...(data.workFor !== undefined ? { workFor: data.workFor } : {}),
|
|
...(data.clientWebsiteCount !== undefined
|
|
? { clientWebsiteCount: data.clientWebsiteCount }
|
|
: {}),
|
|
...(data.foundVia !== undefined ? { foundVia: data.foundVia } : {}),
|
|
...(data.mcpSetupIntent !== undefined
|
|
? { mcpSetupIntent: data.mcpSetupIntent }
|
|
: {}),
|
|
...(completedAt !== undefined ? { completedAt } : {}),
|
|
updatedAt: now,
|
|
};
|
|
|
|
await db
|
|
.insert(userOnboardingAnswers)
|
|
.values({
|
|
userId: context.userId,
|
|
organizationId: context.organizationId,
|
|
interestedFeatures: JSON.stringify(data.interestedFeatures ?? []),
|
|
workFor: data.workFor,
|
|
clientWebsiteCount: data.clientWebsiteCount,
|
|
foundVia: data.foundVia,
|
|
mcpSetupIntent: data.mcpSetupIntent,
|
|
completedAt,
|
|
updatedAt: now,
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: userOnboardingAnswers.userId,
|
|
set,
|
|
});
|
|
|
|
return { ok: true };
|
|
});
|
|
|
|
// Records that the one-time "connect Search Console" nudge has been shown and
|
|
// resolved (dismissed or acted on) so it never reappears for this user.
|
|
export const dismissGscNudge = createServerFn({ method: "POST" })
|
|
.middleware(requireAuthenticatedContext)
|
|
.handler(async ({ context }) => {
|
|
const now = new Date().toISOString();
|
|
await db
|
|
.insert(userOnboardingAnswers)
|
|
.values({
|
|
userId: context.userId,
|
|
organizationId: context.organizationId,
|
|
gscNudgeDismissedAt: now,
|
|
updatedAt: now,
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: userOnboardingAnswers.userId,
|
|
set: { gscNudgeDismissedAt: now, updatedAt: now },
|
|
});
|
|
|
|
return { ok: true };
|
|
});
|