diff --git a/apps/web/app/(navigation)/chat/[id]/page.tsx b/apps/web/app/(navigation)/chat/[id]/page.tsx new file mode 100644 index 00000000..9bb2038b --- /dev/null +++ b/apps/web/app/(navigation)/chat/[id]/page.tsx @@ -0,0 +1,39 @@ +"use client" + +import { useEffect } from "react" +import { useParams } from "next/navigation" +import { usePersistentChat } from "@/stores" +import { ChatMessages } from "@/components/views/chat/chat-messages" + +export default function ChatPage() { + const params = useParams() + const { setCurrentChatId, getCurrentChat } = usePersistentChat() + + const chatId = params.id as string + + useEffect(() => { + if (chatId) { + setCurrentChatId(chatId) + } + }, [chatId, setCurrentChatId]) + + const currentChat = getCurrentChat() + + return ( +
+
+
+

+ {currentChat?.title || "New Chat"} +

+
+ +
+
+ +
+
+
+
+ ) +} diff --git a/apps/web/app/(navigation)/layout.tsx b/apps/web/app/(navigation)/layout.tsx new file mode 100644 index 00000000..da5a87fb --- /dev/null +++ b/apps/web/app/(navigation)/layout.tsx @@ -0,0 +1,57 @@ +"use client" + +import { Header } from "@/components/header" +import { AddMemoryView } from "@/components/views/add-memory" +import { useEffect, useState } from "react" + +export default function NavigationLayout({ + children, +}: { + children: React.ReactNode +}) { + const [showAddMemoryView, setShowAddMemoryView] = useState(false) + useEffect(() => { + const handleKeydown = (event: KeyboardEvent) => { + const target = event.target as HTMLElement + const isInputField = + target.tagName === "INPUT" || + target.tagName === "TEXTAREA" || + target.isContentEditable || + target.closest('[contenteditable="true"]') + + if (isInputField) return + + // add memory shortcut + if ( + event.key === "c" && + !event.ctrlKey && + !event.metaKey && + !event.altKey && + !event.shiftKey + ) { + event.preventDefault() + setShowAddMemoryView(true) + } + } + + document.addEventListener("keydown", handleKeydown) + + return () => { + document.removeEventListener("keydown", handleKeydown) + } + }, []) + return ( +
+
+
setShowAddMemoryView(true)} /> +
+ {children} + {showAddMemoryView && ( + setShowAddMemoryView(false)} + /> + )} +
+ ) +} diff --git a/apps/web/app/(navigation)/page.tsx b/apps/web/app/(navigation)/page.tsx new file mode 100644 index 00000000..b8c81985 --- /dev/null +++ b/apps/web/app/(navigation)/page.tsx @@ -0,0 +1,81 @@ +"use client" + +import { useOnboardingStorage } from "@hooks/use-onboarding-storage" +import { useAuth } from "@lib/auth-context" +import { ChevronsDown, LoaderIcon } from "lucide-react" +import { useRouter } from "next/navigation" +import { useEffect } from "react" +import { InstallPrompt } from "@/components/install-prompt" +import { ChatInput } from "@/components/chat-input" +import { BackgroundPlus } from "@ui/components/grid-plus" +import { Memories } from "@/components/memories" + +export default function Page() { + const { user, session } = useAuth() + const { shouldShowOnboarding, isLoading: onboardingLoading } = + useOnboardingStorage() + const router = useRouter() + + useEffect(() => { + const url = new URL(window.location.href) + const authenticateChromeExtension = url.searchParams.get( + "extension-auth-success", + ) + + if (authenticateChromeExtension) { + const sessionToken = session?.token + const userData = { + email: user?.email, + name: user?.name, + userId: user?.id, + } + + if (sessionToken && userData?.email) { + const encodedToken = encodeURIComponent(sessionToken) + window.postMessage({ token: encodedToken, userData }, "*") + url.searchParams.delete("extension-auth-success") + window.history.replaceState({}, "", url.toString()) + } + } + }, [user, session]) + + useEffect(() => { + if (user && !onboardingLoading && shouldShowOnboarding()) { + router.push("/onboarding") + } + }, [user, shouldShowOnboarding, onboardingLoading, router]) + + if (!user || onboardingLoading) { + return ( +
+
+ +

Loading...

+
+
+ ) + } + + if (shouldShowOnboarding()) { + return null + } + + return ( +
+
+ +
+ +
+ +
+ +

Scroll down to see memories

+
+
+ + + +
+ ) +} diff --git a/apps/web/app/(navigation)/settings/billing/page.tsx b/apps/web/app/(navigation)/settings/billing/page.tsx new file mode 100644 index 00000000..2b8e6ba0 --- /dev/null +++ b/apps/web/app/(navigation)/settings/billing/page.tsx @@ -0,0 +1,12 @@ +"use client" +import { BillingView } from "@/components/views/billing" +export default function BillingPage() { + return ( +
+

+ Billing & Subscription +

+ +
+ ) +} \ No newline at end of file diff --git a/apps/web/app/(navigation)/settings/integrations/page.tsx b/apps/web/app/(navigation)/settings/integrations/page.tsx new file mode 100644 index 00000000..7fedd143 --- /dev/null +++ b/apps/web/app/(navigation)/settings/integrations/page.tsx @@ -0,0 +1,10 @@ +"use client" +import { IntegrationsView } from "@/components/views/integrations" +export default function IntegrationsPage() { + return ( +
+

Integrations

+ +
+ ) +} \ No newline at end of file diff --git a/apps/web/app/(navigation)/settings/layout.tsx b/apps/web/app/(navigation)/settings/layout.tsx new file mode 100644 index 00000000..342e640c --- /dev/null +++ b/apps/web/app/(navigation)/settings/layout.tsx @@ -0,0 +1,52 @@ +"use client" + +import { Button } from "@ui/components/button" +import { useRouter, usePathname } from "next/navigation" +import { cn } from "@repo/lib/utils" + +export default function SettingsPageLayout({ + children, +}: { + children: React.ReactNode +}) { + const router = useRouter() + const pathname = usePathname() + + const navItems = [ + { label: "Profile", path: "/settings" }, + { label: "Integrations", path: "/settings/integrations" }, + { label: "Billing", path: "/settings/billing" }, + { label: "Support", path: "/settings/support" }, + ] + + return ( +
+
+
+ + {children} +
+
+
+ ) +} diff --git a/apps/web/app/(navigation)/settings/page.tsx b/apps/web/app/(navigation)/settings/page.tsx new file mode 100644 index 00000000..c9046fba --- /dev/null +++ b/apps/web/app/(navigation)/settings/page.tsx @@ -0,0 +1,12 @@ +"use client" +import { ProfileView } from "@/components/views/profile" +export default function ProfilePage() { + return ( +
+

+ Profile Settings +

+ +
+ ) +} \ No newline at end of file diff --git a/apps/web/app/(navigation)/settings/support/page.tsx b/apps/web/app/(navigation)/settings/support/page.tsx new file mode 100644 index 00000000..de4b3c78 --- /dev/null +++ b/apps/web/app/(navigation)/settings/support/page.tsx @@ -0,0 +1,125 @@ +"use client" + +import { Button } from "@repo/ui/components/button" +import { HeadingH3Bold } from "@repo/ui/text/heading/heading-h3-bold" +import { ExternalLink, Mail, MessageCircle } from "lucide-react" + +export default function SupportPage() { + return ( +
+

+ Support & Help +

+ +
+ {/* Contact Options */} +
+ Get Help +

+ Need assistance? We're here to help! Choose the best way to reach + us. +

+ +
+ + + +
+
+ + {/* FAQ Section */} +
+ + Frequently Asked Questions + + +
+
+

+ How do I upgrade to Pro? +

+

+ Go to the Billing tab in settings and click "Upgrade to Pro". + You'll be redirected to our secure payment processor. +

+
+ +
+

+ What's included in the Pro plan? +

+

+ Pro includes 5,000 memories (vs 200 in free), 10 connections to + external services like Google Drive and Notion, advanced search + features, and priority support. +

+
+ +
+

+ How do connections work? +

+

+ Connections let you sync documents from Google Drive, Notion, + and OneDrive automatically. supermemory will index and make them + searchable. +

+
+ +
+

+ Can I cancel my subscription anytime? +

+

+ Yes! You can cancel anytime from the Billing tab. Your Pro + features will remain active until the end of your billing + period. +

+
+
+
+ + {/* Feedback Section */} +
+ + Feedback & Feature Requests + +

+ Have ideas for new features or improvements? We'd love to hear from + you! +

+ + +
+
+
+ ) +} \ No newline at end of file diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index e6d9094d..622eb2c1 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,69 +1,71 @@ -import type { Metadata } from "next"; -import { Inter, JetBrains_Mono } from "next/font/google"; -import "../globals.css"; -import "@ui/globals.css"; -import { AuthProvider } from "@lib/auth-context"; -import { ErrorTrackingProvider } from "@lib/error-tracking"; -import { PostHogProvider } from "@lib/posthog"; -import { QueryProvider } from "@lib/query-client"; -import { AutumnProvider } from "autumn-js/react"; -import { Suspense } from "react"; -import { Toaster } from "sonner"; -import { TourProvider } from "@/components/tour"; -import { MobilePanelProvider } from "@/lib/mobile-panel-context"; +import type { Metadata } from "next" +import { Space_Grotesk } from "next/font/google" +import "../globals.css" +import "@ui/globals.css" +import { AuthProvider } from "@lib/auth-context" +import { ErrorTrackingProvider } from "@lib/error-tracking" +import { PostHogProvider } from "@lib/posthog" +import { QueryProvider } from "@lib/query-client" +import { AutumnProvider } from "autumn-js/react" +import { Suspense } from "react" +import { Toaster } from "sonner" +import { MobilePanelProvider } from "@/lib/mobile-panel-context" +import { NuqsAdapter } from "nuqs/adapters/next/app" +import { ThemeProvider } from "@/lib/theme-provider" -import { ViewModeProvider } from "@/lib/view-mode-context"; +import { ViewModeProvider } from "@/lib/view-mode-context" -const sans = Inter({ +const font = Space_Grotesk({ subsets: ["latin"], variable: "--font-sans", -}); - -const mono = JetBrains_Mono({ - subsets: ["latin"], - variable: "--font-mono", -}); +}) export const metadata: Metadata = { metadataBase: new URL("https://app.supermemory.ai"), description: "Your memories, wherever you are", title: "supermemory app", -}; +} export default function RootLayout({ children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode }>) { return ( - - - + + - - - - - - - - {children} - - - - - - - - - + + + + + + + + + {children} + + + + + + + + + + - ); + ) } diff --git a/apps/web/app/onboarding/animated-text.tsx b/apps/web/app/onboarding/animated-text.tsx new file mode 100644 index 00000000..c32abb10 --- /dev/null +++ b/apps/web/app/onboarding/animated-text.tsx @@ -0,0 +1,62 @@ +"use client" +import { useEffect } from "react" +import { TextEffect } from "@/components/text-effect" + +export function AnimatedText({ + children, + trigger, + delay, +}: { + children: string + trigger: boolean + delay: number +}) { + const blurSlideVariants = { + container: { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { staggerChildren: 0.01 }, + }, + exit: { + transition: { staggerChildren: 0.01, staggerDirection: 1 }, + }, + }, + item: { + hidden: { + opacity: 0, + filter: "blur(10px) brightness(0%)", + y: 0, + }, + visible: { + opacity: 1, + y: 0, + filter: "blur(0px) brightness(100%)", + transition: { + duration: 0.4, + }, + }, + exit: { + opacity: 0, + y: -30, + filter: "blur(10px) brightness(0%)", + transition: { + duration: 0.3, + }, + }, + }, + } + + return ( + + {children} + + ) +} diff --git a/apps/web/app/onboarding/bio-form.tsx b/apps/web/app/onboarding/bio-form.tsx new file mode 100644 index 00000000..d985a775 --- /dev/null +++ b/apps/web/app/onboarding/bio-form.tsx @@ -0,0 +1,97 @@ +"use client" + +import { Textarea } from "@ui/components/textarea" +import { useOnboarding } from "./onboarding-context" +import { useState } from "react" +import { Button } from "@ui/components/button" +import { AnimatePresence, motion } from "motion/react" +import { NavMenu } from "./nav-menu" +import { $fetch } from "@lib/api" + +export function BioForm() { + const [bio, setBio] = useState("") + const { totalSteps, nextStep, getStepNumberFor } = useOnboarding() + + function handleNext() { + const trimmed = bio.trim() + if (!trimmed) { + nextStep() + return + } + + nextStep() + void $fetch("@post/memories", { + body: { + content: trimmed, + containerTags: ["sm_project_default"], + metadata: { sm_source: "consumer" }, + }, + }).catch((error) => { + console.error("Failed to save onboarding bio memory:", error) + }) + } + return ( +
+
+ +

+ Step {getStepNumberFor("bio")} of {totalSteps} +

+
+

+ Tell us about yourself +

+

+ What should Supermemory know about you? +

+
+