diff --git a/apps/web/components/brain-home/brain-home-view.tsx b/apps/web/components/brain-home/brain-home-view.tsx index 3501c5e8..18e5b6f1 100644 --- a/apps/web/components/brain-home/brain-home-view.tsx +++ b/apps/web/components/brain-home/brain-home-view.tsx @@ -9,6 +9,7 @@ import { useQueryState } from "nuqs" import { useSettingsModal } from "@/components/settings/settings-modal" import { useBrainTrial } from "@/hooks/use-brain-trial" import { TrialSetupBanner } from "@/components/trial-setup-banner" +import { BrainSetupModal } from "@/components/brain-setup-modal" import { useTrialStatus } from "@/hooks/use-trial-status" import { dmSans125ClassName } from "@/lib/fonts" import { useViewMode } from "@/lib/view-mode-context" @@ -172,7 +173,8 @@ export function BrainHomeView() { const o = useBrainOverview() const trial = useBrainTrial() const board = useConnectionsBoard() - const { needsSetup } = useTrialStatus() + const { data: trialStatus } = useTrialStatus() + const { org: activeOrg } = useAuth() // Rows with no reported state (older orgs, pre-Slack) don't count or render. const milestones = [ ...(o.researchStatus != null ? [o.researchStatus === "done"] : []), @@ -184,6 +186,10 @@ export function BrainHomeView() { ] const milestonesDone = milestones.filter(Boolean).length const milestonesTotal = milestones.length + // Positive gate: a slow trial request would otherwise prompt an expired org. + const showSetupPrompt = Boolean( + board.slack && !board.slack.connected && trialStatus?.active, + ) const showTimeline = !o.loading && (trial.state !== "none" || milestonesDone < milestonesTotal) @@ -199,7 +205,7 @@ export function BrainHomeView() { setupTotal={milestonesTotal} lastUpdatedAt={o.lastUpdatedAt} /> - {board.slack && !board.slack.connected && !needsSetup && } +
{board.showBoard && } @@ -221,6 +227,7 @@ export function BrainHomeView() {
+ {showSetupPrompt && } ) } diff --git a/apps/web/components/brain-home/connections-board.tsx b/apps/web/components/brain-home/connections-board.tsx index 3c79256e..7124781a 100644 --- a/apps/web/components/brain-home/connections-board.tsx +++ b/apps/web/components/brain-home/connections-board.tsx @@ -18,11 +18,11 @@ const BACKEND = process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" const MCP_BASE = `${BACKEND}/brain/mcp-connections` -const cardStyle = { +export const cardStyle = { boxShadow: "0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset", } -const tileStyle = { +export const tileStyle = { boxShadow: "0px 1px 2px 0px rgba(0,43,87,0.1), inset 0px 0px 0px 1px rgba(43,49,67,0.08), inset 0px 1px 1px 0px rgba(0,0,0,0.08), inset 0px 2px 4px 0px rgba(0,0,0,0.02)", } diff --git a/apps/web/components/brain-setup-modal.tsx b/apps/web/components/brain-setup-modal.tsx new file mode 100644 index 00000000..f20a1816 --- /dev/null +++ b/apps/web/components/brain-setup-modal.tsx @@ -0,0 +1,178 @@ +"use client" + +import { Check } from "lucide-react" +import { useEffect, useState } from "react" +import { Dialog, DialogContent, DialogTitle } from "@ui/components/dialog" +import { cn } from "@lib/utils" +import { SlackMark } from "@/components/brain-connector-icons" +import { cardStyle, tileStyle } from "@/components/brain-home/connections-board" +import { analytics } from "@/lib/analytics" +import { COMPANY_BRAIN_CAL_HREF } from "@/lib/cal" +import { dmSans125ClassName, dmSansClassName } from "@/lib/fonts" + +const BACKEND = + process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" +const dismissKey = (orgId: string) => `sm_brain_setup_modal_dismissed:${orgId}` + +const CALL_FEATURES = [ + "We install Slack with you, live", + "Connect Gmail, Notion, Linear and the rest", + "Wire up plugins and answer your questions", +] + +export function BrainSetupModal({ + enabled, + orgId, +}: { + enabled: boolean + orgId: string | undefined +}) { + const [open, setOpen] = useState(false) + + useEffect(() => { + // Also closes if the org stops being eligible while the dialog is open. + if (!enabled || !orgId) { + setOpen(false) + return + } + let dismissed = false + try { + dismissed = localStorage.getItem(dismissKey(orgId)) === "1" + } catch {} + if (dismissed) return + setOpen(true) + analytics.brainSetupModalSeen() + }, [enabled, orgId]) + + const close = () => { + setOpen(false) + if (!orgId) return + try { + localStorage.setItem(dismissKey(orgId), "1") + } catch {} + } + + return ( + !next && close()}> + + Get set up + +
+

+ Get set up +

+

+ Your brain is ready. Let's get it working where your team is. +

+
+ + + + + + +
+
+ ) +} diff --git a/apps/web/components/company-brain-header.tsx b/apps/web/components/company-brain-header.tsx index e7c9258a..07c8215b 100644 --- a/apps/web/components/company-brain-header.tsx +++ b/apps/web/components/company-brain-header.tsx @@ -37,6 +37,7 @@ import { FeedbackModal } from "@/components/feedback-modal" import { OrgPlanBadge, resolveOrgPlan } from "@/components/org-plan-badge" import { SlackMark } from "@/components/brain-connector-icons" import { BrainTrialPill } from "@/components/brain-trial-pill" +import { SetupCallLink } from "@/components/setup-call-link" import { GraphIcon } from "@/components/integration-icons" import { SpaceSelector } from "@/components/space-selector" import { UserProfileMenu } from "@/components/user-profile-menu" @@ -538,29 +539,7 @@ export function CompanyBrainHeader({ )} - {canInvite && ( - - - - - - Invite teammates - - - )} + + + + Book a setup call + + + ) +} diff --git a/apps/web/components/user-profile-menu.tsx b/apps/web/components/user-profile-menu.tsx index ea034d73..fc43255d 100644 --- a/apps/web/components/user-profile-menu.tsx +++ b/apps/web/components/user-profile-menu.tsx @@ -14,6 +14,7 @@ import { authClient } from "@lib/auth" import { useRouter } from "next/navigation" import { Brain, + CalendarClock, LogOut, Settings, Settings2, @@ -29,6 +30,7 @@ import { useOrgOnboarding } from "@hooks/use-org-onboarding" import { useTokenUsage } from "@/hooks/use-token-usage" import { useSettingsModal } from "@/components/settings/settings-modal" import { useHasCompanyBrain } from "@/hooks/use-company-brain" +import { COMPANY_BRAIN_CAL_HREF } from "@/lib/cal" import { useViewMode } from "@/lib/view-mode-context" export function UserProfileMenu({ @@ -213,6 +215,24 @@ export function UserProfileMenu({ ) : null} + {isCompanyBrain ? ( + + + analytics.brainSetupCallClicked({ surface: "user_menu" }) + } + > + + Book a setup call + + + ) : null} safeCapture("brain_trial_checkout_started"), brainTrialCheckoutAbandoned: () => safeCapture("brain_trial_checkout_abandoned"), + brainSetupCallClicked: (props: { surface: SetupCallSurface }) => + safeCapture("brain_setup_call_clicked", props), + brainSetupModalSeen: () => safeCapture("brain_setup_modal_seen"), + brainSetupModalPicked: (props: { choice: "slack" | "call" }) => + safeCapture("brain_setup_modal_picked", props), } diff --git a/apps/web/lib/cal.ts b/apps/web/lib/cal.ts new file mode 100644 index 00000000..86cdf2ee --- /dev/null +++ b/apps/web/lib/cal.ts @@ -0,0 +1,8 @@ +export const COMPANY_BRAIN_CAL_HREF = + "https://cal.com/team/supermemory/company-brain" + +export type SetupCallSurface = + | "research_rail" + | "setup_modal" + | "header" + | "user_menu"