mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
fix: input disable when onboarding org creation (#855)
This commit is contained in:
parent
76b0f2746b
commit
b8e5376b99
5 changed files with 136 additions and 66 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 : []
|
||||
|
||||
if (existingOrgs.length > 0) {
|
||||
analytics.onboardingNameSubmitted({
|
||||
name_length: trimmed.length,
|
||||
})
|
||||
goToStep("greeting")
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex p-6 justify-between items-center">
|
||||
<div className="flex items-center z-10!">
|
||||
|
|
@ -24,13 +40,24 @@ export function InitialHeader({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
variant="newDefault"
|
||||
className="rounded-2xl text-base gap-1 h-11! z-10!"
|
||||
size={"lg"}
|
||||
>
|
||||
Memory API <span className="text-xs mt-[4px]">↗</span>
|
||||
</Button>
|
||||
<div className="flex items-center gap-3 z-10!">
|
||||
{showSkipOnboarding && !isLoading && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSkip}
|
||||
className="text-sm text-white/40 hover:text-white/70 transition-colors cursor-pointer"
|
||||
>
|
||||
Skip Onboarding
|
||||
</button>
|
||||
)}
|
||||
<Button
|
||||
variant="newDefault"
|
||||
className="rounded-2xl text-base gap-1 h-11!"
|
||||
size="lg"
|
||||
>
|
||||
Memory API <span className="text-xs mt-[4px]">↗</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,20 @@ import { useRouter } from "next/navigation"
|
|||
import { cn } from "@lib/utils"
|
||||
import { dmSansClassName } from "@/lib/fonts"
|
||||
import { useLocalStorageUsername } from "@hooks/use-local-storage-username"
|
||||
import { useOrgOnboarding } from "@hooks/use-org-onboarding"
|
||||
import { analytics } from "@/lib/analytics"
|
||||
|
||||
export function SetupHeader() {
|
||||
const { user } = useAuth()
|
||||
const router = useRouter()
|
||||
const localStorageUsername = useLocalStorageUsername()
|
||||
const { markOrgOnboarded, isLoading: isOrgLoading } = useOrgOnboarding()
|
||||
|
||||
const handleSkip = () => {
|
||||
markOrgOnboarded()
|
||||
analytics.onboardingCompleted()
|
||||
router.push("/")
|
||||
}
|
||||
|
||||
const displayName =
|
||||
user?.displayUsername || localStorageUsername || user?.name || ""
|
||||
|
|
@ -59,9 +68,21 @@ export function SetupHeader() {
|
|||
</span>
|
||||
<span className="text-white/50 font-medium shrink-0">Setup</span>
|
||||
</nav>
|
||||
{user && (
|
||||
<UserProfileMenu className="z-10" avatarClassName="border-border" />
|
||||
)}
|
||||
<div className="flex items-center gap-3 z-10">
|
||||
{!isOrgLoading && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSkip}
|
||||
className={cn(
|
||||
"text-sm text-white/40 hover:text-white/70 transition-colors cursor-pointer",
|
||||
dmSansClassName(),
|
||||
)}
|
||||
>
|
||||
Skip Onboarding
|
||||
</button>
|
||||
)}
|
||||
{user && <UserProfileMenu avatarClassName="border-border" />}
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { motion } from "motion/react"
|
||||
import { cn } from "@lib/utils"
|
||||
import { LabeledInput } from "@ui/input/labeled-input"
|
||||
import { Button } from "@ui/components/button"
|
||||
|
||||
|
|
@ -17,7 +18,10 @@ export function InputStep({
|
|||
}: InputStepProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className="text-center min-w-[250px] flex flex-col"
|
||||
className={cn(
|
||||
"text-center min-w-[250px] flex flex-col",
|
||||
isSubmitting && "pointer-events-none",
|
||||
)}
|
||||
style={{ gap: "24px" }}
|
||||
initial={{
|
||||
opacity: 0,
|
||||
|
|
@ -53,20 +57,27 @@ export function InputStep({
|
|||
className="w-full flex-1"
|
||||
inputProps={{
|
||||
defaultValue: name,
|
||||
disabled: isSubmitting,
|
||||
onKeyDown: (e) => {
|
||||
if (e.key === "Enter") {
|
||||
handleSubmit()
|
||||
}
|
||||
if (e.key !== "Enter") return
|
||||
e.preventDefault()
|
||||
if (isSubmitting) return
|
||||
handleSubmit()
|
||||
},
|
||||
className: "!text-white placeholder:!text-[#525966] !h-[40px] pl-4",
|
||||
}}
|
||||
onChange={(e) => setName((e.target as HTMLInputElement).value)}
|
||||
onChange={(e) => {
|
||||
if (isSubmitting) return
|
||||
setName((e.target as HTMLInputElement).value)
|
||||
}}
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(0deg, rgba(91, 126, 245, 0.04) 0%, rgba(91, 126, 245, 0.04) 100%)",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={isSubmitting}
|
||||
className={`rounded-[8px] w-8 h-8 p-2 absolute right-1 border-[0.5px] border-[#161F2C] hover:cursor-pointer hover:scale-[0.95] active:scale-[0.95] transition-transform ${
|
||||
isSubmitting ? "scale-[0.90]" : ""
|
||||
}`}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue