diff --git a/apps/web/app/(app)/onboarding/welcome/layout.tsx b/apps/web/app/(app)/onboarding/welcome/layout.tsx index 2b889d385..8a6853688 100644 --- a/apps/web/app/(app)/onboarding/welcome/layout.tsx +++ b/apps/web/app/(app)/onboarding/welcome/layout.tsx @@ -11,6 +11,7 @@ import { } from "react" import { useRouter, useSearchParams } from "next/navigation" import { useOnboardingContext, type MemoryFormData } from "../layout" +import { useAuth } from "@lib/auth-context" import { analytics } from "@/lib/analytics" export const WELCOME_STEPS = [ @@ -51,13 +52,17 @@ export default function WelcomeLayout({ children }: { children: ReactNode }) { const searchParams = useSearchParams() const { name, setName, memoryFormData, setMemoryFormData } = useOnboardingContext() + const { organizations } = useAuth() + const hasOrgs = Array.isArray(organizations) && organizations.length > 0 const stepParam = searchParams.get("step") - const currentStep: WelcomeStep = WELCOME_STEPS.includes( + const resolvedStep: WelcomeStep = WELCOME_STEPS.includes( stepParam as WelcomeStep, ) ? (stepParam as WelcomeStep) : "input" + const currentStep: WelcomeStep = + resolvedStep === "input" && hasOrgs ? "greeting" : resolvedStep const [isSubmitting, setIsSubmitting] = useState(false) const [showWelcomeContent, setShowWelcomeContent] = useState(false) diff --git a/apps/web/app/(app)/onboarding/welcome/page.tsx b/apps/web/app/(app)/onboarding/welcome/page.tsx index e0df24455..ac3ea599a 100644 --- a/apps/web/app/(app)/onboarding/welcome/page.tsx +++ b/apps/web/app/(app)/onboarding/welcome/page.tsx @@ -1,5 +1,6 @@ "use client" +import { useRef } from "react" import { motion, AnimatePresence } from "motion/react" import { cn } from "@lib/utils" @@ -102,66 +103,70 @@ export default function WelcomePage() { } = useWelcomeContext() const { refetchOrganizations, setActiveOrg } = useAuth() + const submitLockRef = useRef(false) const handleSubmit = async () => { - localStorage.setItem("username", name) - if (name.trim()) { - setIsSubmitting(true) + const trimmed = name.trim() + if (!trimmed) return + if (submitLockRef.current) return + submitLockRef.current = true + localStorage.setItem("username", trimmed) + setIsSubmitting(true) - try { - await authClient.updateUser({ - displayUsername: name.trim(), - username: generateUsername(name.trim()), - }) + try { + await authClient.updateUser({ + displayUsername: trimmed, + username: generateUsername(trimmed), + }) - const refetchResult = await refetchOrganizations() - const refetchData = ( - refetchResult as { data?: unknown[] | null | undefined } - )?.data - const existingOrgs = Array.isArray(refetchData) ? refetchData : [] + const refetchResult = await refetchOrganizations() + const refetchData = ( + refetchResult as { data?: unknown[] | null | undefined } + )?.data + const existingOrgs = Array.isArray(refetchData) ? refetchData : [] - if (existingOrgs.length > 0) { - analytics.onboardingNameSubmitted({ - name_length: name.trim().length, - }) - goToStep("greeting") - return - } + if (existingOrgs.length > 0) { + analytics.onboardingNameSubmitted({ + name_length: trimmed.length, + }) + goToStep("greeting") + return + } - const uniqueSlug = generateOrgSlug(name.trim()) - const completedAt = new Date().toISOString() - const newOrg = await authClient.organization.create({ - name: name.trim(), - slug: uniqueSlug, - metadata: { - signupSource: "consumer", - webOnboarding: { - completedAt: null, - steps: { - welcomeInput: { - startedAt: completedAt, - completedAt, - data: {}, - }, + const uniqueSlug = generateOrgSlug(trimmed) + const completedAt = new Date().toISOString() + const newOrg = await authClient.organization.create({ + name: trimmed, + slug: uniqueSlug, + metadata: { + signupSource: "consumer", + webOnboarding: { + completedAt: null, + steps: { + welcomeInput: { + startedAt: completedAt, + completedAt, + data: {}, }, }, }, - }) + }, + }) - await setActiveOrg(newOrg.slug) + await setActiveOrg(newOrg.slug) - analytics.onboardingNameSubmitted({ name_length: name.trim().length }) - goToStep("greeting") - } catch (error) { - console.error("Onboarding submit failed:", error) - toast.error( - error instanceof Error - ? error.message - : "Could not set up your workspace. Please try again.", - ) - } finally { - setIsSubmitting(false) - } + analytics.onboardingNameSubmitted({ name_length: trimmed.length }) + goToStep("greeting") + } catch (error) { + console.error("Onboarding submit failed:", error) + toast.error( + error instanceof Error + ? error.message + : "Could not set up your workspace. Please try again.", + ) + } finally { + submitLockRef.current = false + setIsSubmitting(false) } } @@ -212,6 +217,7 @@ export default function WelcomePage() { showUserSupermemory={ currentStep === "features" || currentStep === "memories" } + showSkipOnboarding={currentStep !== "input"} name={name} /> diff --git a/apps/web/components/initial-header.tsx b/apps/web/components/initial-header.tsx index 91edfa572..5d8aebc96 100644 --- a/apps/web/components/initial-header.tsx +++ b/apps/web/components/initial-header.tsx @@ -1,14 +1,30 @@ +"use client" + import { Logo } from "@ui/assets/Logo" import { Button } from "@ui/components/button" +import { useRouter } from "next/navigation" +import { useOrgOnboarding } from "@hooks/use-org-onboarding" +import { analytics } from "@/lib/analytics" export function InitialHeader({ showUserSupermemory, + showSkipOnboarding, name, }: { showUserSupermemory?: boolean + showSkipOnboarding?: boolean name?: string }) { + const router = useRouter() + const { markOrgOnboarded, isLoading } = useOrgOnboarding() const userName = name ? `${name.split(" ")[0]}'s` : "My" + + const handleSkip = () => { + markOrgOnboarded() + analytics.onboardingCompleted() + router.push("/") + } + return (