Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 87 additions & 4 deletions apps/web/app/(app)/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client"

import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useQueryClient } from "@tanstack/react-query"
import { useRouter, useSearchParams } from "next/navigation"
import { toast } from "sonner"
import { useAuth } from "@lib/auth-context"
Expand All @@ -17,6 +18,7 @@ import {
type SourcesValues,
} from "@/components/onboarding-brain/step-sources"
import { StepIngest } from "@/components/onboarding-brain/step-ingest"
import { CompanyBrainOnboarding } from "@/components/onboarding-brain/company-brain-onboarding"
import { useFeatureFlagEnabled } from "posthog-js/react"
import {
StepTeam,
Expand All @@ -32,10 +34,14 @@ import {
generateOrgSlug,
generateUsername,
workspaceDomainFromEmail,
workspaceNameFromDomain,
workspaceNameFromEmail,
type CompanyBrainConfirmResult,
} from "@/components/onboarding-brain/types"

const STORAGE_KEY = "supermemory-brain-onboarding-v1"
const BACKEND =
process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai"

const countsAsConnectedSource = (state: unknown) =>
state === "connected" || state === "waitlist"
Expand All @@ -53,6 +59,7 @@ const getErrorMessage = (error: unknown, fallback: string) => {
export default function BrainOnboardingPage() {
const router = useRouter()
const params = useSearchParams()
const queryClient = useQueryClient()
const { user, org, organizations, setActiveOrg, refetchOrganizations } =
useAuth()

Expand Down Expand Up @@ -235,9 +242,14 @@ export default function BrainOnboardingPage() {
const [creatingOrg, setCreatingOrg] = useState(false)
const creatingOrgRef = useRef(false)

const ensureOrg = useCallback(async () => {
if (!forceCreate && organizations && organizations.length > 0) return
const name = (about.workspaceName || suggestedWorkspaceName).trim()
const ensureOrg = useCallback(
async (domainOverride?: string): Promise<boolean> => {
if (!forceCreate && organizations && organizations.length > 0) return false
const name = (
domainOverride
? workspaceNameFromDomain(domainOverride)
: about.workspaceName || suggestedWorkspaceName
).trim()
const slug = generateOrgSlug(name)
const effectiveMode = allowTeam ? mode : "personal"
const metadata: BrainMetadata & { signupSource: string } = {
Expand All @@ -246,7 +258,9 @@ export default function BrainOnboardingPage() {
brainMode: effectiveMode,
brainWorkspaceName: name,
brainWorkspaceDomain:
effectiveMode === "team" ? about.workspaceDomain || domain : null,
effectiveMode === "team"
? domainOverride || about.workspaceDomain || domain
: null,
brainContainerTag: containerTag,
...(about.about.trim() ? { brainAbout: about.about.trim() } : {}),
}
Expand Down Expand Up @@ -281,6 +295,7 @@ export default function BrainOnboardingPage() {
url.searchParams.delete("name")
router.replace(url.pathname + url.search, { scroll: false })
}
return true
}, [
organizations,
about,
Expand Down Expand Up @@ -320,6 +335,61 @@ export default function BrainOnboardingPage() {
}
}, [ensureOrg, goNext, forceCreate, organizations, router])

// Company Brain (team) onboarding is a single research surface, no stepper.
const isCompanyBrain = allowTeam && mode === "team"

const handleBrainConfirm = useCallback(
async (confirmedDomain: string): Promise<CompanyBrainConfirmResult> => {
if (creatingOrgRef.current) return { ok: false }
creatingOrgRef.current = true
setCreatingOrg(true)
try {
const workspaceName = workspaceNameFromDomain(confirmedDomain)
setAbout((a) => ({
...a,
workspaceDomain: confirmedDomain,
workspaceName: workspaceName || a.workspaceName,
}))
const orgCreated = await ensureOrg(confirmedDomain)
// Re-entering onboarding on an existing org ("Try onboarding") must
// kick research from the client. New orgs rely on the signup hook after
// provisioning — a duplicate /start races and can strand the DO task.
if (!orgCreated) {
try {
await fetch(`${BACKEND}/brain/research/start`, {
method: "POST",
credentials: "include",
headers: {
"content-type": "application/json",
"X-App-Source": "nova",
},
body: JSON.stringify({ domain: confirmedDomain }),
})
} catch {}
}
queryClient.invalidateQueries({ queryKey: ["brain-research-status"] })
analytics.onboardingWorkspaceCreated({
mode: "team",
has_about: false,
has_domain: Boolean(confirmedDomain),
})
return { ok: true, serverSchedulesResearch: orgCreated }
} catch (e) {
const message = getErrorMessage(e, "Organization was not created.")
console.error("Failed to create organization:", e)
analytics.onboardingWorkspaceCreateFailed({ error: message })
toast.error("Organization was not created", {
description: "Please try again.",
})
return { ok: false }
} finally {
creatingOrgRef.current = false
setCreatingOrg(false)
}
},
[ensureOrg, queryClient],
)

const [sendingInvites, setSendingInvites] = useState(false)
const sendingInvitesRef = useRef(false)

Expand Down Expand Up @@ -375,6 +445,19 @@ export default function BrainOnboardingPage() {

const mcpUrl = "https://mcp.supermemory.ai/mcp"

if (isCompanyBrain) {
return (
<CompanyBrainOnboarding
name={about.name || user?.name || ""}
avatarUrl={user?.image ?? null}
domain={about.workspaceDomain || domain || ""}
submitting={creatingOrg}
onConfirm={handleBrainConfirm}
onDone={finish}
/>
)
}

return (
<BrainShell
step={step}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/app-experience.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ export function AppExperience() {
{isDashboardShell && showBottomNav && (
<div className="pointer-events-none fixed inset-x-0 bottom-0 z-20 h-64 bg-gradient-to-t from-[#05080D] via-[#05080D]/95 to-transparent" />
)}
{isDashboardShell && (
{isDashboardShell && !isCompanyBrain && (
<div
className={cn(
"pointer-events-none fixed inset-x-0 z-30",
Expand Down
Loading
Loading