Files
multica/packages/views/onboarding/steps/step-source.tsx
Naiyuan Qing 7b3cad664b revert(self-host): remove source channel reporting (#4799)
* Revert "test(onboarding): cover official source reporting controls (#4782)"

This reverts commit fc88c7720f.

* Revert "fix(self-host): restore official source report endpoint (#4781)"

This reverts commit ad1afdd48d.

* Revert "feat(self-host): collect anonymous source channels mul-3878 (#4741)"

This reverts commit 26142d74aa.
2026-07-01 17:53:16 +08:00

103 lines
4.1 KiB
TypeScript

"use client";
import {
Briefcase,
CalendarDays,
Globe,
HelpCircle,
MoreHorizontal,
Newspaper,
Users,
} from "lucide-react";
import type { QuestionnaireAnswers, Source } from "@multica/core/onboarding";
import {
GoogleIcon,
LinkedInIcon,
OpenAIIcon,
XIcon,
YouTubeIcon,
GitHubIcon,
} from "../components/brand-icons";
import { StepQuestion, type QuestionOption } from "./step-question";
import { useT } from "../../i18n";
/**
* Step 1 — "How did you hear about Multica?" Pure attribution, does
* not influence the agent template recommendation.
*/
export function StepSource({
answers,
onChange,
onAdvance,
onSkip,
onBack,
}: {
answers: QuestionnaireAnswers;
onChange: (patch: Partial<QuestionnaireAnswers>) => void;
onAdvance: () => void;
onSkip: () => void;
onBack?: () => void;
}) {
const { t } = useT("onboarding");
const options: QuestionOption[] = [
{ slug: "friends_colleagues", icon: <Users className="h-4 w-4" />, label: t(($) => $.questions.source.friends_colleagues) },
{ slug: "search", icon: <GoogleIcon className="h-[18px] w-[18px]" />, label: t(($) => $.questions.source.search) },
{ slug: "social_x", icon: <XIcon className="h-[15px] w-[15px]" />, label: t(($) => $.questions.source.social_x) },
{ slug: "social_linkedin", icon: <LinkedInIcon className="h-[18px] w-[18px]" />, label: t(($) => $.questions.source.social_linkedin) },
{ slug: "social_youtube", icon: <YouTubeIcon className="h-[18px] w-[18px]" />, label: t(($) => $.questions.source.social_youtube) },
{ slug: "social_github", icon: <GitHubIcon className="h-[18px] w-[18px]" />, label: t(($) => $.questions.source.social_github) },
{ slug: "social_other", icon: <Globe className="h-4 w-4" />, label: t(($) => $.questions.source.social_misc) },
{ slug: "blog_newsletter", icon: <Newspaper className="h-4 w-4" />, label: t(($) => $.questions.source.blog_newsletter) },
{ slug: "ai_assistant", icon: <OpenAIIcon className="h-[16px] w-[16px]" />, label: t(($) => $.questions.source.ai_assistant) },
{ slug: "from_work", icon: <Briefcase className="h-4 w-4" />, label: t(($) => $.questions.source.from_work) },
{ slug: "event_conference", icon: <CalendarDays className="h-4 w-4" />, label: t(($) => $.questions.source.event_conference) },
{ slug: "dont_remember", icon: <HelpCircle className="h-4 w-4" />, label: t(($) => $.questions.source.dont_remember) },
{ slug: "other", icon: <MoreHorizontal className="h-4 w-4" />, label: t(($) => $.questions.source.other), isOther: true },
];
// Single-select on the primary acquisition source. The server schema
// keeps `source` as a string array for back-compat with v2 multi-
// select rows, but the UI only ever commits a one-element array —
// primary-source attribution is the documented industry default for
// HDYHAU prompts (Fairing, Recast, HockeyStack) and keeps channel
// weights clean for analytics.
const selected: readonly string[] = answers.source?.[0] ? [answers.source[0]] : [];
const pick = (slug: string) => {
const typed = slug as Source;
onChange({
source: [typed],
// Switching off Other clears the free-text input so a stale
// value can't leak into the next pick. Picking Other keeps the
// existing text so the user can finish typing without losing
// their input.
source_other: typed === "other" ? answers.source_other : null,
source_skipped: false,
});
};
return (
<StepQuestion
step="source"
number={1}
eyebrow={t(($) => $.questions.eyebrow_about_you)}
question={t(($) => $.questions.source.question)}
options={options}
selectedSlugs={selected}
otherValue={answers.source_other ?? ""}
onOtherChange={(v) => onChange({ source_other: v })}
otherPlaceholder={t(($) => $.questions.source.other_placeholder)}
onAnswer={pick}
onAdvance={onAdvance}
onSkip={() => {
onChange({ source: [], source_other: null, source_skipped: true });
onSkip();
}}
onBack={onBack}
/>
);
}
StepSource.displayName = "StepSource";