diff --git a/apps/web/app/(navigation)/page.tsx b/apps/web/app/(navigation)/page.tsx index 212f33c0..056e0e8a 100644 --- a/apps/web/app/(navigation)/page.tsx +++ b/apps/web/app/(navigation)/page.tsx @@ -42,7 +42,7 @@ export default function Page() { useEffect(() => { if (user && !onboardingLoading && shouldShowOnboarding()) { - router.push("/onboarding") + router.push("/onboarding/welcome") } }, [user, shouldShowOnboarding, onboardingLoading, router]) diff --git a/apps/web/app/onboarding/animated-text.tsx b/apps/web/app/onboarding-old/animated-text.tsx similarity index 100% rename from apps/web/app/onboarding/animated-text.tsx rename to apps/web/app/onboarding-old/animated-text.tsx diff --git a/apps/web/app/onboarding/bio-form.tsx b/apps/web/app/onboarding-old/bio-form.tsx similarity index 100% rename from apps/web/app/onboarding/bio-form.tsx rename to apps/web/app/onboarding-old/bio-form.tsx diff --git a/apps/web/app/onboarding/extension-form.tsx b/apps/web/app/onboarding-old/extension-form.tsx similarity index 100% rename from apps/web/app/onboarding/extension-form.tsx rename to apps/web/app/onboarding-old/extension-form.tsx diff --git a/apps/web/app/onboarding/floating-orbs.tsx b/apps/web/app/onboarding-old/floating-orbs.tsx similarity index 100% rename from apps/web/app/onboarding/floating-orbs.tsx rename to apps/web/app/onboarding-old/floating-orbs.tsx diff --git a/apps/web/app/onboarding/intro.tsx b/apps/web/app/onboarding-old/intro.tsx similarity index 100% rename from apps/web/app/onboarding/intro.tsx rename to apps/web/app/onboarding-old/intro.tsx diff --git a/apps/web/app/onboarding/mcp-form.tsx b/apps/web/app/onboarding-old/mcp-form.tsx similarity index 100% rename from apps/web/app/onboarding/mcp-form.tsx rename to apps/web/app/onboarding-old/mcp-form.tsx diff --git a/apps/web/app/onboarding/name-form.tsx b/apps/web/app/onboarding-old/name-form.tsx similarity index 100% rename from apps/web/app/onboarding/name-form.tsx rename to apps/web/app/onboarding-old/name-form.tsx diff --git a/apps/web/app/onboarding/nav-menu.tsx b/apps/web/app/onboarding-old/nav-menu.tsx similarity index 100% rename from apps/web/app/onboarding/nav-menu.tsx rename to apps/web/app/onboarding-old/nav-menu.tsx diff --git a/apps/web/app/onboarding/onboarding-background.tsx b/apps/web/app/onboarding-old/onboarding-background.tsx similarity index 100% rename from apps/web/app/onboarding/onboarding-background.tsx rename to apps/web/app/onboarding-old/onboarding-background.tsx diff --git a/apps/web/app/onboarding/onboarding-context.tsx b/apps/web/app/onboarding-old/onboarding-context.tsx similarity index 100% rename from apps/web/app/onboarding/onboarding-context.tsx rename to apps/web/app/onboarding-old/onboarding-context.tsx diff --git a/apps/web/app/onboarding/onboarding-form.tsx b/apps/web/app/onboarding-old/onboarding-form.tsx similarity index 100% rename from apps/web/app/onboarding/onboarding-form.tsx rename to apps/web/app/onboarding-old/onboarding-form.tsx diff --git a/apps/web/app/onboarding/page.tsx b/apps/web/app/onboarding-old/page.tsx similarity index 100% rename from apps/web/app/onboarding/page.tsx rename to apps/web/app/onboarding-old/page.tsx diff --git a/apps/web/app/onboarding/progress-bar.tsx b/apps/web/app/onboarding-old/progress-bar.tsx similarity index 100% rename from apps/web/app/onboarding/progress-bar.tsx rename to apps/web/app/onboarding-old/progress-bar.tsx diff --git a/apps/web/app/onboarding/welcome.tsx b/apps/web/app/onboarding-old/welcome.tsx similarity index 100% rename from apps/web/app/onboarding/welcome.tsx rename to apps/web/app/onboarding-old/welcome.tsx diff --git a/apps/web/app/onboarding/setup/chat-sidebar.tsx b/apps/web/app/onboarding/setup/chat-sidebar.tsx new file mode 100644 index 00000000..774ecdf1 --- /dev/null +++ b/apps/web/app/onboarding/setup/chat-sidebar.tsx @@ -0,0 +1,117 @@ +"use client" + +import { useState } from "react" +import { motion } from "motion/react" +import NovaOrb from "@/components/nova/nova-orb" +import { Button } from "@ui/components/button" +import { PanelRightCloseIcon, SendIcon } from "lucide-react" + +export function ChatSidebar() { + const [message, setMessage] = useState("") + const [isChatOpen, setIsChatOpen] = useState(true) + + const handleSend = () => { + console.log("Message:", message) + setMessage("") + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault() + handleSend() + } + } + + const toggleChat = () => { + setIsChatOpen(!isChatOpen) + } + + if (!isChatOpen) { + return ( + + + Chat with Nova + + + ) + } + + return ( + + + + Close chat + +
+
+ + + + Waiting for your input + +
+
+ +
+
{ + e.preventDefault() + if (message.trim()) { + handleSend() + } + }} + > + setMessage(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Chat with your Supermemory" + className="w-full text-foreground placeholder:text-foreground/20 rounded-sm outline-none resize-none text-base leading-relaxed bg-transparent px-2 h-10" + /> +
+ +
+
+
+
+ ) +} diff --git a/apps/web/app/onboarding/setup/header.tsx b/apps/web/app/onboarding/setup/header.tsx new file mode 100644 index 00000000..e452a57b --- /dev/null +++ b/apps/web/app/onboarding/setup/header.tsx @@ -0,0 +1,43 @@ +import { motion } from "motion/react" +import { Logo } from "@ui/assets/Logo" +import { useAuth } from "@lib/auth-context" +import { useEffect, useState } from "react" +import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar" + +export function SetupHeader() { + const { user } = useAuth() + const [name, setName] = useState("") + + useEffect(() => { + const storedName = + localStorage.getItem("username") || localStorage.getItem("userName") || "" + setName(storedName) + }, []) + + return ( + +
+ + {name && ( +
+

{name}'s

+

+ supermemory +

+
+ )} +
+ {user && ( + + + {user?.name?.charAt(0)} + + )} +
+ ) +} diff --git a/apps/web/app/onboarding/setup/integrations-step.tsx b/apps/web/app/onboarding/setup/integrations-step.tsx new file mode 100644 index 00000000..e0c0f57b --- /dev/null +++ b/apps/web/app/onboarding/setup/integrations-step.tsx @@ -0,0 +1,133 @@ +"use client" + +import { useState } from "react" +import { Button } from "@ui/components/button" +import { MCPDetailView } from "@/components/mcp-detail-view" + +const integrationCards = [ + { + title: "Capture", + description: "Add the Chrome extension for one-click saves", + icon: ( +
+ Chrome +
+ ), + }, + { + title: "Connect to AI", + description: "Set up once and use your memory in Cursor, Claude, etc", + icon: ( +
+ MCP +
+ ), + }, + { + title: "Connect", + description: "Link Notion, Google Drive, or OneDrive to import your docs", + icon: ( +
+ Connectors +
+ ), + }, + { + title: "Import", + description: + "Bring in X/Twitter bookmarks, and turn them into useful memories", + icon: ( +
+ X +
+ ), + }, +] + +interface IntegrationsStepProps { + onBack: () => void +} + +export function IntegrationsStep({ onBack }: IntegrationsStepProps) { + const [selectedCard, setSelectedCard] = useState(null) + + if (selectedCard === "Connect to AI") { + return setSelectedCard(null)} /> + } + return ( +
+
+

+ Build your personal memory +

+

+ Your supermemory comes alive when you capture and connect what's + important +

+
+ +
+ {integrationCards.map((card) => { + const isClickable = card.title === "Connect to AI" + + if (isClickable) { + return ( + + ) + } + + return ( +
+
+

{card.title}

+

+ {card.description} +

+
+
{card.icon}
+
+ ) + })} +
+ +
+ + +
+
+ ) +} diff --git a/apps/web/app/onboarding/setup/page.tsx b/apps/web/app/onboarding/setup/page.tsx new file mode 100644 index 00000000..ce913585 --- /dev/null +++ b/apps/web/app/onboarding/setup/page.tsx @@ -0,0 +1,94 @@ +"use client" + +import { SetupHeader } from "./header" +import { AnimatePresence, motion } from "motion/react" +import { RelatableQuestion } from "./relatable-question" +import { IntegrationsStep } from "./integrations-step" +import { ChatSidebar } from "./chat-sidebar" +import { useState } from "react" + +function AnimatedGradientBackground() { + return ( +
+ + + +
+ ) +} + +export default function SetupPage() { + const [currentStep, setCurrentStep] = useState<"relatable" | "integrations">( + "relatable", + ) + + const handleContinueOrSkip = () => { + setCurrentStep("integrations") + } + + const handleBack = () => { + setCurrentStep("relatable") + } + + return ( +
+ +
+ +
+
+ {currentStep === "relatable" && ( + + )} + {currentStep === "integrations" && ( + + )} +
+ + + + +
+
+
+ ) +} diff --git a/apps/web/app/onboarding/setup/relatable-question.tsx b/apps/web/app/onboarding/setup/relatable-question.tsx new file mode 100644 index 00000000..f642d92a --- /dev/null +++ b/apps/web/app/onboarding/setup/relatable-question.tsx @@ -0,0 +1,110 @@ +"use client" + +import { useState } from "react" +import { motion } from "motion/react" +import { Button } from "@ui/components/button" + +const relatableOptions = [ + { + emoji: "😔", + text: "I always forget what I save in my twitter bookmarks", + }, + { + emoji: "😭", + text: "Going through e-books manually is so tedious", + }, + { + emoji: "🥲", + text: "I always have to feed every AI app with my data", + }, + { + emoji: "😵‍💫", + text: "Referring meeting notes makes my AI chat hallucinate", + }, + { + emoji: "🫤", + text: "I save nothing on my browser, it's just useless", + }, +] + +interface RelatableQuestionProps { + onContinueOrSkip: () => void +} + +export function RelatableQuestion({ + onContinueOrSkip, +}: RelatableQuestionProps) { + const [selectedOptions, setSelectedOptions] = useState([]) + + return ( + + + Which of these sound most relatable? + + +
+ {relatableOptions.map((option, index) => ( + + ))} +
+
+
+ +
+
+
+ ) +} diff --git a/apps/web/app/onboarding/welcome/continue-step.tsx b/apps/web/app/onboarding/welcome/continue-step.tsx new file mode 100644 index 00000000..783ae700 --- /dev/null +++ b/apps/web/app/onboarding/welcome/continue-step.tsx @@ -0,0 +1,32 @@ +import { Button } from "@ui/components/button" +import { motion } from "motion/react" + +interface ContinueStepProps { + setCurrentStep: ( + step: "input" | "greeting" | "welcome" | "username" | "features", + ) => void +} + +export function ContinueStep({ setCurrentStep }: ContinueStepProps) { + return ( + +

+ I'm built with Supermemory's super fast memory API, so you never have to + worry about forgetting what matters across your AI apps. +

+ +
+ ) +} diff --git a/apps/web/app/onboarding/welcome/features-step.tsx b/apps/web/app/onboarding/welcome/features-step.tsx new file mode 100644 index 00000000..cecdcc03 --- /dev/null +++ b/apps/web/app/onboarding/welcome/features-step.tsx @@ -0,0 +1,97 @@ +import { motion } from "framer-motion" +import { Button } from "@ui/components/button" +import Link from "next/link" + +export function FeaturesStep() { + return ( + +

+ What I can do for you +

+ +
+
+
+ Brain icon +
+
+

+ Remember every context +

+

+ I keep track of what you've saved and shared with your + supermemory. +

+
+
+ +
+
+ Search icon +
+
+

+ Find when you need it +

+

+ I surface the right memories inside your supermemory, superfast. +

+
+
+ +
+
+ Growth icon +
+
+

+ Grow with your supermemory +

+

+ I learn and personalize over time, so every interaction feels + natural. +

+
+
+
+ + + + +
+ ) +} diff --git a/apps/web/app/onboarding/welcome/greeting-step.tsx b/apps/web/app/onboarding/welcome/greeting-step.tsx new file mode 100644 index 00000000..04cc708e --- /dev/null +++ b/apps/web/app/onboarding/welcome/greeting-step.tsx @@ -0,0 +1,23 @@ +import { motion } from "framer-motion" + +interface GreetingStepProps { + name: string +} + +export function GreetingStep({ name }: GreetingStepProps) { + return ( + +

+ Hi {name}, I'm Nova +

+
+ ) +} diff --git a/apps/web/app/onboarding/welcome/input-step.tsx b/apps/web/app/onboarding/welcome/input-step.tsx new file mode 100644 index 00000000..365a786a --- /dev/null +++ b/apps/web/app/onboarding/welcome/input-step.tsx @@ -0,0 +1,86 @@ +import { motion } from "motion/react" +import { LabeledInput } from "@ui/input/labeled-input" +import { Button } from "@ui/components/button" + +interface InputStepProps { + name: string + setName: (name: string) => void + handleSubmit: () => void + isSubmitting: boolean +} + +export function InputStep({ + name, + setName, + handleSubmit, + isSubmitting, +}: InputStepProps) { + return ( + +

+ What should I call you? +

+
+ { + if (e.key === "Enter") { + handleSubmit() + } + }, + }} + onChange={(e) => 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%)", + }} + /> + +
+
+ ) +} diff --git a/apps/web/app/onboarding/welcome/page.tsx b/apps/web/app/onboarding/welcome/page.tsx new file mode 100644 index 00000000..f3d8c7d0 --- /dev/null +++ b/apps/web/app/onboarding/welcome/page.tsx @@ -0,0 +1,147 @@ +"use client" + +import { useState, useEffect } from "react" +import { motion, AnimatePresence } from "motion/react" +import { InitialHeader } from "@/components/initial-header" +import { useAuth } from "@lib/auth-context" +import { InputStep } from "./input-step" +import { GreetingStep } from "./greeting-step" +import { WelcomeStep } from "./welcome-step" +import { ContinueStep } from "./continue-step" +import { FeaturesStep } from "./features-step" +import { Logo } from "@ui/assets/Logo" +import NovaOrb from "@/components/nova/nova-orb" +import { cn } from "@lib/utils" + +type OnboardStep = "input" | "greeting" | "welcome" | "username" | "features" + +function UserSupermemory({ name }: { name: string }) { + return ( + + +
+

{name}'s

+

+ supermemory +

+
+
+ ) +} + +export default function OnboardPage() { + const { user } = useAuth() + const [name, setName] = useState(user?.name ?? "") + const [currentStep, setCurrentStep] = useState("input") + const [isSubmitting, setIsSubmitting] = useState(false) + + const handleSubmit = () => { + localStorage.setItem("username", name) + if (name.trim()) { + setIsSubmitting(true) + setCurrentStep("greeting") + setIsSubmitting(false) + } + } + + useEffect(() => { + if (user?.name) { + setName(user.name) + localStorage.setItem("username", user.name) + } + }, [user?.name]) + + useEffect(() => { + const timers: NodeJS.Timeout[] = [] + + switch (currentStep) { + case "greeting": + timers.push(setTimeout(() => setCurrentStep("welcome"), 2000)) + break + case "welcome": + timers.push(setTimeout(() => setCurrentStep("username"), 2000)) + break + } + + return () => { + timers.forEach(clearTimeout) + } + }, [currentStep]) + + return ( +
+ +
+ + + + + {currentStep === "username" && } + + + + {currentStep === "input" && ( + + )} + + {currentStep === "greeting" && } + + {currentStep === "welcome" && } + + {currentStep === "username" && ( + setCurrentStep(step as OnboardStep)} + /> + )} + {currentStep === "features" && } + + +
+
+ ) +} diff --git a/apps/web/app/onboarding/welcome/welcome-step.tsx b/apps/web/app/onboarding/welcome/welcome-step.tsx new file mode 100644 index 00000000..5df84e70 --- /dev/null +++ b/apps/web/app/onboarding/welcome/welcome-step.tsx @@ -0,0 +1,17 @@ +import { motion } from "motion/react" + +export function WelcomeStep() { + return ( + +

Welcome to

+
+ ) +} diff --git a/apps/web/components/initial-header.tsx b/apps/web/components/initial-header.tsx new file mode 100644 index 00000000..59fb7a75 --- /dev/null +++ b/apps/web/components/initial-header.tsx @@ -0,0 +1,33 @@ +import { Logo } from "@ui/assets/Logo" +import { Button } from "@ui/components/button" + +export function InitialHeader({ + showUserSupermemory, + name, +}: { + showUserSupermemory?: boolean + name?: string +}) { + return ( +
+
+ + {showUserSupermemory && ( +
+

{name}'s

+

+ supermemory +

+
+ )} +
+ +
+ ) +} diff --git a/apps/web/components/mcp-detail-view.tsx b/apps/web/components/mcp-detail-view.tsx new file mode 100644 index 00000000..539bf2ef --- /dev/null +++ b/apps/web/components/mcp-detail-view.tsx @@ -0,0 +1,306 @@ +"use client" + +import { useState, useEffect } from "react" +import { Button } from "@ui/components/button" +import { CircleCheckIcon, CopyIcon } from "lucide-react" +import { toast } from "sonner" +import { analytics } from "@/lib/analytics" + +const clients = { + cursor: "Cursor", + claude: "Claude Desktop", + vscode: "VSCode", + cline: "Cline", + "gemini-cli": "Gemini CLI", + "claude-code": "Claude Code", + "mcp-url": "MCP URL", + "roo-cline": "Roo Cline", + witsy: "Witsy", + enconvo: "Enconvo", +} as const + +interface MCPDetailViewProps { + onBack: () => void +} + +export function MCPDetailView({ onBack }: MCPDetailViewProps) { + const [selectedClient, setSelectedClient] = useState< + keyof typeof clients | null + >(null) + const [selectedProject] = useState("sm_project_default") + const [mcpUrlTab, setMcpUrlTab] = useState<"oneClick" | "manual">("oneClick") + const [isCopied, setIsCopied] = useState(false) + + useEffect(() => { + analytics.mcpViewOpened() + }, []) + + function generateInstallCommand() { + if (!selectedClient) return "" + + let command = `npx -y install-mcp@latest https://api.supermemory.ai/mcp --client ${selectedClient} --oauth=yes` + + const projectIdForCommand = selectedProject.replace(/^sm_project_/, "") + command += ` --project ${projectIdForCommand}` + + return command + } + + const copyToClipboard = () => { + const command = generateInstallCommand() + navigator.clipboard.writeText(command) + analytics.mcpInstallCmdCopied() + toast.success("Copied to clipboard!") + } + + return ( +
+
+ +
+ +
+

+ Connect your AI to supermemory MCP +

+ +
+
+ +

+ MCP connects your AI apps to create and use memories directly +

+
+
+ +

+ Auto-fetch the right context from anything you've saved +

+
+
+ +

+ One-time setup, seamless integration across your workflow +

+
+
+ +
+
+
+ 1 +
+
+

+ Select your AI client +

+
+ {Object.entries(clients) + .slice(0, 7) + .map(([key, clientName]) => ( + + ))} +
+

+ *You can connect to all of these, setup is different for each + one +

+
+
+ +
+
+ 2 +
+
+

+ Copy the installation command +

+ {selectedClient ? ( +
+ {selectedClient === "mcp-url" ? ( +
+
+
+ + +
+
+ + {mcpUrlTab === "oneClick" ? ( +
+

+ Use this URL to quickly configure supermemory in + your AI assistant +

+
+ + +
+
+ ) : ( +
+

+ Add this configuration to your MCP settings file + with authentication +

+
+
+															
+																{`{
+  "supermemory-mcp": {
+    "command": "npx",
+    "args": ["-y", "mcp-remote", "https://api.supermemory.ai/mcp"],
+    "env": {},
+    "headers": {
+      "Authorization": "Bearer your-api-key-here"
+    }
+  }
+}`}
+															
+														
+ +
+

+ The API key is included as a Bearer token in the + Authorization header +

+
+ )} +
+ ) : ( +
+
+ + +
+

+ Copy and run this command in your terminal to install + the MCP server. +

+
+ )} +
+ ) : ( +

+ The command will be provided once you select an AI client + above. +

+ )} +
+
+ +
+
+ 3 +
+
+

+ Run command in your terminal +

+

+ Execute the copied command in your terminal to complete the + setup. +

+
+
+
+
+
+ ) +} diff --git a/apps/web/components/nova/bg-grad.tsx b/apps/web/components/nova/bg-grad.tsx new file mode 100644 index 00000000..66aa437c --- /dev/null +++ b/apps/web/components/nova/bg-grad.tsx @@ -0,0 +1,150 @@ +"use client" + +interface BgGradProps { + size?: number + className?: string +} + +function BgGrad({ size = 400, className = "" }: BgGradProps) { + return ( +
+ {/* Large blue orb - right */} +
+ + {/* Large blue orb - left */} +
+ + {/* Rotated blue orb - top left */} +
+
+
+
+
+ + {/* Rotated blue orb - top right */} +
+
+
+
+
+ + {/* Rotated blue orb - bottom left */} +
+
+
+
+
+ + {/* Central rotated orb */} +
+
+
+
+
+ + {/* Bottom blue orb */} +
+
+ ) +} + +export default BgGrad diff --git a/apps/web/components/nova/nova-orb.tsx b/apps/web/components/nova/nova-orb.tsx new file mode 100644 index 00000000..3fb8d430 --- /dev/null +++ b/apps/web/components/nova/nova-orb.tsx @@ -0,0 +1,42 @@ +"use client" + +import BgGrad from "./bg-grad" +import { motion } from "motion/react" + +interface NovaOrbProps { + size?: number + className?: string +} + +function NovaOrb({ size = 200, className = "" }: NovaOrbProps) { + return ( +
+
+
+ + + +
+
+
+ ) +} + +export default NovaOrb diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 7bab1d21..f7b4b6df 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -54,6 +54,6 @@ export default async function proxy(request: Request) { export const config = { matcher: [ - "/((?!_next/static|_next/image|images|icon.png|monitoring|opengraph-image.png|ingest|api|login|api/emails).*)", + "/((?!_next/static|_next/image|images|icon.png|monitoring|opengraph-image.png|bg-rectangle.png|onboarding|ingest|api|login|api/emails).*)", ], } diff --git a/apps/web/public/bg-rectangle.png b/apps/web/public/bg-rectangle.png new file mode 100644 index 00000000..b1faa25d Binary files /dev/null and b/apps/web/public/bg-rectangle.png differ diff --git a/apps/web/public/onboarding/bg-gradient-0.png b/apps/web/public/onboarding/bg-gradient-0.png new file mode 100644 index 00000000..f050f4f4 Binary files /dev/null and b/apps/web/public/onboarding/bg-gradient-0.png differ diff --git a/apps/web/public/onboarding/bg-gradient-1.png b/apps/web/public/onboarding/bg-gradient-1.png new file mode 100644 index 00000000..5bcbc997 Binary files /dev/null and b/apps/web/public/onboarding/bg-gradient-1.png differ diff --git a/apps/web/public/onboarding/chrome.png b/apps/web/public/onboarding/chrome.png new file mode 100644 index 00000000..edcfad7f Binary files /dev/null and b/apps/web/public/onboarding/chrome.png differ diff --git a/apps/web/public/onboarding/connectors.png b/apps/web/public/onboarding/connectors.png new file mode 100644 index 00000000..0ce66316 Binary files /dev/null and b/apps/web/public/onboarding/connectors.png differ diff --git a/apps/web/public/onboarding/human-brain.png b/apps/web/public/onboarding/human-brain.png new file mode 100644 index 00000000..9a52a42e Binary files /dev/null and b/apps/web/public/onboarding/human-brain.png differ diff --git a/apps/web/public/onboarding/plant.png b/apps/web/public/onboarding/plant.png new file mode 100644 index 00000000..e9651dae Binary files /dev/null and b/apps/web/public/onboarding/plant.png differ diff --git a/apps/web/public/onboarding/search.png b/apps/web/public/onboarding/search.png new file mode 100644 index 00000000..e60d6872 Binary files /dev/null and b/apps/web/public/onboarding/search.png differ diff --git a/apps/web/public/onboarding/x.png b/apps/web/public/onboarding/x.png new file mode 100644 index 00000000..a981fc25 Binary files /dev/null and b/apps/web/public/onboarding/x.png differ diff --git a/packages/ui/button/external-auth.tsx b/packages/ui/button/external-auth.tsx index 04f05af3..ce7db8be 100644 --- a/packages/ui/button/external-auth.tsx +++ b/packages/ui/button/external-auth.tsx @@ -15,9 +15,14 @@ export function ExternalAuthButton({ return ( - -
- ) : ( -
-
- - Welcome to {" "} - - - - {heroText} - -
+ + +
+ + ) : ( + +
+ {params.get("error") && ( +
+ Error: {params.get("error")}. Please try again! +
+ )} - {params.get("error") && ( -
- Error: {params.get("error")}. Please try again! -
+
+ {process.env.NEXT_PUBLIC_HOST_ID === "supermemory" || + !process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED ? ( +
+ + Google + + + + + + } + authProvider="Google" + className="w-full" + disabled={isLoading} + onClick={() => { + if (isLoading) return; + setIsLoading(true); + posthog.capture("login_attempt", { + method: "social", + provider: "google", + }); + setPendingLoginMethod("google"); + signIn + .social({ + callbackURL: getCallbackURL(), + provider: "google", + }) + .finally(() => { + setIsLoading(false); + }); + }} + /> + {lastUsedMethod === "google" && ( +
+ + Last used + +
+ )} +
+ ) : null} + {process.env.NEXT_PUBLIC_HOST_ID === "supermemory" || + !process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED ? ( +
+ + Github + + + + + + + + + + } + authProvider="Github" + className="w-full" + disabled={isLoading} + onClick={() => { + if (isLoading) return; + setIsLoading(true); + posthog.capture("login_attempt", { + method: "social", + provider: "github", + }); + setPendingLoginMethod("github"); + signIn + .social({ + callbackURL: getCallbackURL(), + provider: "github", + }) + .finally(() => { + setIsLoading(false); + }); + }} + /> + {lastUsedMethod === "github" && ( +
+ + Last used + +
+ )} +
+ ) : null} +
+ + + +
+ { + setEmail(e.target.value); + error && setError(null); + }, + required: true, + value: email, + }} + inputType="email" + /> + +
+ + {lastUsedMethod === "magic_link" && ( +
+ + Last used + +
+ )} +
+ + + + By continuing, you agree to our{" "} + + + Terms + {" "} + and{" "} + + Privacy Policy + + . + + +
+
)} - -
-
- { - setEmail(e.target.value); - error && setError(null); - }, - required: true, - value: email, - }} - inputType="email" - label="Email" - /> - -
- - {lastUsedMethod === "magic_link" && ( -
- - Last used - -
- )} -
-
-
- - {process.env.NEXT_PUBLIC_HOST_ID === "supermemory" || - !process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED || - !process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED ? ( - - ) : null} - -
- {process.env.NEXT_PUBLIC_HOST_ID === "supermemory" || - !process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED ? ( -
- - Google - - - - - - } - authProvider="Google" - className="w-full" - disabled={isLoading} - onClick={() => { - if (isLoading) return; - setIsLoading(true); - setError(null); - posthog.capture("login_attempt", { - method: "social", - provider: "google", - }); - setPendingLoginMethod("google"); - signIn - .social({ - callbackURL: getCallbackURL(), - provider: "google", - }) - .catch((error) => { - console.error("Google login error:", error); - posthog.capture("login_failed", { - method: "social", - provider: "google", - error: - error instanceof Error - ? error.message - : "Unknown error", - is_network_error: isNetworkError(error), - }); - setError(getErrorMessage(error)); - }) - .finally(() => { - setIsLoading(false); - }); - }} - /> - {lastUsedMethod === "google" && ( -
- - Last used - -
- )} -
- ) : null} - {process.env.NEXT_PUBLIC_HOST_ID === "supermemory" || - !process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED ? ( -
- - Github - - - - - - - - - - } - authProvider="Github" - className="w-full" - disabled={isLoading} - onClick={() => { - if (isLoading) return; - setIsLoading(true); - setError(null); - posthog.capture("login_attempt", { - method: "social", - provider: "github", - }); - setPendingLoginMethod("github"); - signIn - .social({ - callbackURL: getCallbackURL(), - provider: "github", - }) - .catch((error) => { - console.error("GitHub login error:", error); - posthog.capture("login_failed", { - method: "social", - provider: "github", - error: - error instanceof Error - ? error.message - : "Unknown error", - is_network_error: isNetworkError(error), - }); - setError(getErrorMessage(error)); - }) - .finally(() => { - setIsLoading(false); - }); - }} - /> - {lastUsedMethod === "github" && ( -
- - Last used - -
- )} -
- ) : null} -
- - - By continuing, you agree to our{" "} - - - Terms - {" "} - and{" "} - - Privacy Policy - - . - - -
- )} - + +
+ ); }