newish get started page

This commit is contained in:
Dhravya Shah 2025-09-18 20:15:59 -07:00
parent 53eeadccca
commit 8e784b3889
8 changed files with 606 additions and 0 deletions

View file

@ -0,0 +1,362 @@
import { AnimatePresence, motion } from "framer-motion"
import { Brain, Chrome, ExternalLink, Plus } from "lucide-react"
import { useEffect, useState } from "react"
import { ConnectAIModal } from "@/components/connect-ai-modal"
import { analytics } from "@/lib/analytics"
interface GetStartedProps {
setShowAddMemoryView?: (show: boolean) => void
}
export const GetStarted = ({ setShowAddMemoryView }: GetStartedProps) => {
const [imageLoaded, setImageLoaded] = useState(false)
const [showContent, setShowContent] = useState(false)
const [showConnectAIModal, setShowConnectAIModal] = useState(false)
useEffect(() => {
const img = new Image()
img.onload = () => {
setImageLoaded(true)
setTimeout(() => setShowContent(true), 100)
}
img.src = "/images/onboarding.png"
}, [])
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
duration: 0.8,
ease: [0.25, 0.1, 0.25, 1] as const,
staggerChildren: 0.15,
delayChildren: 0.2,
},
},
}
const leftColumnVariants = {
hidden: {
opacity: 0,
x: -40,
},
visible: {
opacity: 1,
x: 0,
transition: {
duration: 1,
ease: [0.25, 0.1, 0.25, 1] as const,
staggerChildren: 0.1,
},
},
}
const rightColumnVariants = {
hidden: {
opacity: 0,
x: 40,
},
visible: {
opacity: 1,
x: 0,
transition: {
duration: 0.8,
ease: [0.25, 0.1, 0.25, 1] as const,
delay: 0.6,
staggerChildren: 0.1,
},
},
}
const textLineVariants = {
hidden: {
opacity: 0,
y: 30,
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.8,
ease: [0.25, 0.1, 0.25, 1] as const,
},
},
}
const cardVariants = {
hidden: {
opacity: 0,
y: 20,
scale: 0.95,
},
visible: {
opacity: 1,
y: 0,
scale: 1,
transition: {
duration: 0.6,
ease: [0.25, 0.1, 0.25, 1] as const,
},
},
}
const backgroundVariants = {
hidden: {
scale: 1.1,
opacity: 0,
},
visible: {
scale: 1,
opacity: 1,
transition: {
duration: 1.5,
ease: [0.25, 0.1, 0.25, 1] as const,
},
},
}
const handleChromeExtension = () => {
analytics.extensionInstallClicked()
window.open(
"https://chromewebstore.google.com/detail/supermemory/afpgkkipfdpeaflnpoaffkcankadgjfc",
"_blank",
)
}
const handleAddMemory = () => {
setShowConnectAIModal(false)
if (setShowAddMemoryView) {
setShowAddMemoryView(true)
}
}
return (
<div className="fixed inset-0 overflow-hidden bg-black">
<AnimatePresence>
{!imageLoaded && (
<motion.div
className="absolute inset-0 bg-gradient-to-br from-zinc-950 to-zinc-900 flex items-center justify-center z-[1000]"
exit={{ opacity: 0 }}
initial={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<motion.div
animate={{
opacity: [0.3, 1, 0.3],
scale: [1, 1.05, 1],
}}
className="text-white/80 text-xl font-medium tracking-widest"
transition={{
duration: 2,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
}}
>
SUPERMEMORY
</motion.div>
</motion.div>
)}
</AnimatePresence>
{imageLoaded && (
<>
<motion.div
animate="visible"
className="absolute inset-0 bg-cover bg-center bg-no-repeat"
initial="hidden"
style={{
backgroundImage: "url('/images/onboarding.png')",
}}
variants={backgroundVariants}
/>
<motion.div
animate={{ opacity: 1 }}
className="absolute inset-0"
initial={{ opacity: 0 }}
style={{
background: `
radial-gradient(circle at 20% 20%, rgba(99, 102, 241, 0.15) 0%, transparent 50%),
linear-gradient(135deg, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.4) 50%, rgba(0,0,0,0.6) 100%)
`,
}}
transition={{ duration: 1, delay: 0.5 }}
/>
<AnimatePresence>
{showContent && (
<motion.div
animate="visible"
className="relative h-screen grid grid-cols-1 lg:grid-cols-2 items-center p-6 md:p-16 gap-8 lg:gap-16 max-w-7xl mx-auto"
initial="hidden"
variants={containerVariants}
>
{/* Left Column - Quote */}
<motion.div
className="flex flex-col justify-center order-2 lg:order-1"
variants={leftColumnVariants}
>
<motion.div
className="text-2xl md:text-4xl lg:text-5xl xl:text-6xl font-black leading-none text-white mb-2 md:mb-4 tracking-tight font-space-grotesk"
style={{
textShadow: "0 8px 32px rgba(0,0,0,0.6)",
}}
variants={textLineVariants}
>
<span
className="bg-gradient-to-r from-white to-gray-200 bg-clip-text text-transparent"
style={{
filter: "drop-shadow(0 0 20px rgba(255,255,255,0.1))",
}}
>
Intelligence without memory
</span>
</motion.div>
<motion.div
className="text-2xl md:text-4xl lg:text-5xl xl:text-6xl font-black leading-none text-white mb-2 md:mb-4 tracking-tight font-space-grotesk"
style={{
textShadow: "0 8px 32px rgba(0,0,0,0.6)",
}}
variants={textLineVariants}
>
<span
className="bg-gradient-to-r from-white to-gray-200 bg-clip-text text-transparent"
style={{
filter: "drop-shadow(0 0 20px rgba(255,255,255,0.1))",
}}
>
is just sophisticated
</span>
</motion.div>
<motion.div
className="text-2xl md:text-4xl lg:text-5xl xl:text-6xl font-black leading-none tracking-tight font-space-grotesk"
style={{
textShadow: "0 8px 32px rgba(0,0,0,0.6)",
}}
variants={textLineVariants}
>
<span
className="bg-gradient-to-r from-blue-500 via-cyan-500 to-blue-600 bg-clip-text text-transparent"
style={{
filter: "drop-shadow(0 0 20px rgba(59, 130, 246, 0.3))",
}}
>
randomness
</span>
</motion.div>
</motion.div>
{/* Right Column - Actions */}
<motion.div
className="flex md:flex-col justify-center order-1 lg:order-2 flex-col-reverse"
variants={rightColumnVariants}
>
{/* Mobile Chrome Extension Link */}
<motion.p
className="text-sm text-blue-400 hover:text-blue-300 transition-colors underline cursor-pointer mb-4 lg:hidden text-center mt-4 md:mt-0"
onClick={handleChromeExtension}
variants={textLineVariants}
>
Download Chrome extension to use with ChatGPT
</motion.p>
<div className="flex flex-col gap-5">
{/* Chrome Extension Card - Hidden on mobile */}
<motion.button
className="hidden lg:flex bg-white/8 backdrop-blur-xl border border-white/10 rounded-xl p-6 text-left cursor-pointer transition-all duration-300 items-center gap-4 hover:bg-white/12 hover:border-white/20"
onClick={handleChromeExtension}
variants={cardVariants}
whileHover={{
scale: 1.02,
y: -2,
transition: { duration: 0.2, ease: "easeOut" },
}}
whileTap={{ scale: 0.98 }}
>
<Chrome
className="text-amber-500 flex-shrink-0"
size={24}
/>
<div className="flex-1">
<h3 className="text-lg font-semibold text-white mb-1">
Add to Chrome
</h3>
<p className="text-sm text-white/70 leading-relaxed">
Save items and use supermemory with ChatGPT and other
apps. Import your ChatGPT memories, twitter bookmarks,
and other data.
</p>
</div>
<ExternalLink
className="text-white/50 flex-shrink-0"
size={16}
/>
</motion.button>
{/* Connect AI Card */}
<ConnectAIModal
onOpenChange={setShowConnectAIModal}
open={showConnectAIModal}
>
<motion.div
className="bg-white/8 backdrop-blur-xl border border-white/10 rounded-xl p-6 text-left cursor-pointer transition-all duration-300 flex items-center gap-4 hover:bg-white/12 hover:border-white/20"
variants={cardVariants}
whileHover={{
scale: 1.02,
y: -2,
transition: { duration: 0.2, ease: "easeOut" },
}}
whileTap={{ scale: 0.98 }}
>
<Brain
className="text-blue-500 flex-shrink-0"
size={24}
/>
<div className="flex-1">
<h3 className="text-lg font-semibold text-white mb-1">
Connect to AI
</h3>
<p className="text-sm text-white/70 leading-relaxed">
Set up your AI connection for intelligent assistance
</p>
</div>
</motion.div>
</ConnectAIModal>
{/* Add Memory Card */}
<motion.button
className="bg-white/8 backdrop-blur-xl border border-white/10 rounded-xl p-6 text-left cursor-pointer transition-all duration-300 flex items-center gap-4 hover:bg-white/12 hover:border-white/20"
onClick={handleAddMemory}
variants={cardVariants}
whileHover={{
scale: 1.02,
y: -2,
transition: { duration: 0.2, ease: "easeOut" },
}}
whileTap={{ scale: 0.98 }}
>
<Plus
className="text-emerald-500 flex-shrink-0"
size={24}
/>
<div className="flex-1">
<h3 className="text-lg font-semibold text-white mb-1">
Add Memory
</h3>
<p className="text-sm text-white/70 leading-relaxed">
Start building your knowledge base
</p>
</div>
</motion.button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</>
)}
</div>
)
}

View file

@ -0,0 +1,244 @@
import { $fetch } from "@lib/api"
import { useMutation } from "@tanstack/react-query"
import { toast } from "sonner"
// Simplified mutation that doesn't block UI with polling
export const createSimpleAddContentMutation = (
queryClient: any,
onClose?: () => void,
) => {
return useMutation({
mutationFn: async ({
content,
project,
contentType,
}: {
content: string
project: string
contentType: "note" | "link"
}) => {
// Just create the memory, don't wait for processing
const response = await $fetch("@post/documents", {
body: {
content: content,
containerTags: [project],
metadata: {
sm_source: "consumer",
},
},
})
if (response.error) {
throw new Error(
response.error?.message || `Failed to add ${contentType}`,
)
}
return { id: response.data.id, contentType }
},
onMutate: async ({ content, project, contentType }) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({
queryKey: ["documents-with-memories", project],
})
// Snapshot the previous value
const previousMemories = queryClient.getQueryData([
"documents-with-memories",
project,
])
// Create optimistic memory
const tempId = `temp-${Date.now()}`
const optimisticMemory = {
id: tempId,
content: contentType === "link" ? "" : content,
url: contentType === "link" ? content : null,
title:
contentType === "link" ? "Processing..." : content.substring(0, 100),
description:
contentType === "link"
? "Extracting content..."
: "Processing content...",
containerTags: [project],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
status: "processing",
type: contentType,
metadata: {
processingStage: "queued",
processingMessage: "Added to processing queue",
},
memoryEntries: [],
isOptimistic: true,
}
// Optimistically update to include the new memory
queryClient.setQueryData(
["documents-with-memories", project],
(old: any) => {
// Handle infinite query structure
if (old?.pages) {
const newPages = [...old.pages]
if (newPages.length > 0) {
// Add to the first page
const firstPage = { ...newPages[0] }
firstPage.documents = [
optimisticMemory,
...(firstPage.documents || []),
]
newPages[0] = firstPage
} else {
// No pages yet, create the first page
newPages.push({
documents: [optimisticMemory],
pagination: { currentPage: 1, totalPages: 1, totalCount: 1 },
totalCount: 1,
})
}
return {
...old,
pages: newPages,
}
}
// Fallback for regular query structure
const newData = old
? {
...old,
documents: [optimisticMemory, ...(old.documents || [])],
totalCount: (old.totalCount || 0) + 1,
}
: { documents: [optimisticMemory], totalCount: 1 }
return newData
},
)
return { previousMemories, optimisticId: tempId }
},
onError: (error, variables, context) => {
// Roll back on error
if (context?.previousMemories) {
queryClient.setQueryData(
["documents-with-memories", variables.project],
context.previousMemories,
)
}
toast.error(`Failed to add ${variables.contentType}`, {
description: error instanceof Error ? error.message : "Unknown error",
})
},
onSuccess: (data, variables, context) => {
// Show success message
toast.success(
`${variables.contentType === "link" ? "Link" : "Note"} created successfully!`,
)
// Close modal
onClose?.()
// Start polling for this specific memory ID
// The polling will happen in the background and update the optimistic memory when done
startMemoryPolling(
data.id,
variables.project,
context?.optimisticId,
queryClient,
)
},
})
}
// Background polling function
const startMemoryPolling = (
memoryId: string,
project: string,
optimisticId: string | undefined,
queryClient: any,
) => {
const pollMemory = async () => {
try {
const memory = await $fetch(`@get/documents/${memoryId}`)
if (memory.error) {
console.error("Failed to fetch memory status:", memory.error)
return false
}
const isComplete =
memory.data?.status === "done" ||
memory.data?.content ||
memory.data?.memoryEntries?.length > 0
if (isComplete) {
// Replace optimistic memory with real data
queryClient.setQueryData(
["documents-with-memories", project],
(old: any) => {
if (old?.pages) {
// Handle infinite query structure
const newPages = old.pages.map((page: any) => ({
...page,
documents: page.documents.map((doc: any) => {
if (doc.isOptimistic || doc.id === optimisticId) {
// Replace with real memory
return {
...memory.data,
isOptimistic: false,
}
}
return doc
}),
}))
return {
...old,
pages: newPages,
}
}
// Handle regular query structure
return {
...old,
documents: old.documents.map((doc: any) => {
if (doc.isOptimistic || doc.id === optimisticId) {
return {
...memory.data,
isOptimistic: false,
}
}
return doc
}),
}
},
)
return true // Stop polling
}
return false // Continue polling
} catch (error) {
console.error("Error polling memory:", error)
return false // Continue polling
}
}
// Poll every 3 seconds, max 60 attempts (3 minutes)
let attempts = 0
const maxAttempts = 60
const poll = async () => {
if (attempts >= maxAttempts) {
console.log("Memory polling timed out")
return
}
const isComplete = await pollMemory()
attempts++
if (!isComplete && attempts < maxAttempts) {
setTimeout(poll, 3000) // Poll again in 3 seconds
}
}
// Start polling after a short delay
setTimeout(poll, 2000)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB