diff --git a/apps/web/app/(app)/page.tsx b/apps/web/app/(app)/page.tsx index 82948b2a5..cd41476a6 100644 --- a/apps/web/app/(app)/page.tsx +++ b/apps/web/app/(app)/page.tsx @@ -582,6 +582,7 @@ export default function NewPage() { viewMode === "dashboard" || (viewMode === "graph" && isMobile) const isGraphMode = viewMode === "graph" const showBottomNav = isMobile && !!session && !isChatView + const isPublicIntegrations = !session && viewMode === "integrations" return ( @@ -608,7 +609,9 @@ export default function NewPage() { /> )} - {!session && viewMode === "mcp" ? ( + {isPublicIntegrations ? ( + + ) : !session && viewMode === "mcp" ? ( ) : (
) : viewMode === "integrations" ? (
- +
) : viewMode === "mcp" ? ( { - if (isMcpPublicPage) return + if (isGuestPublicAppPage) return if (isRestoring) return if (!session) { router.replace( @@ -44,11 +47,11 @@ export function EnsureWorkspace({ children }: { children: React.ReactNode }) { isRestoring, isOnboarding, router, - isMcpPublicPage, + isGuestPublicAppPage, ]) const showLoading = - !isMcpPublicPage && + !isGuestPublicAppPage && (isRestoring || (!session && !isRestoring) || (session && organizations === null) || diff --git a/apps/web/components/header.tsx b/apps/web/components/header.tsx index cc3afb71e..e47f56c26 100644 --- a/apps/web/components/header.tsx +++ b/apps/web/components/header.tsx @@ -504,7 +504,41 @@ export function Header({ onAddMemory, onOpenSearch }: HeaderProps) { ) } -export function PublicHeader() { +export function PublicHeader({ + variant = "default", +}: { + variant?: "default" | "integrations" +}) { + if (variant === "integrations") { + return ( +
+ + +

+ supermemory +

+ + + + + +
+ ) + } + return (
(null) const [connectingProvider, setConnectingProvider] = @@ -1493,6 +1498,7 @@ export function IntegrationsView() { if (!res.ok) throw new Error("Failed to fetch plugins") return (await res.json()) as { plugins: string[] } }, + enabled: !publicMode, queryKey: ["plugins"], }) @@ -1507,7 +1513,7 @@ export function IntegrationsView() { return response.data as Connection[] }, staleTime: 30 * 1000, - enabled: hasProProduct, + enabled: !publicMode && hasProProduct, }) const { data: apiKeys = [], refetch: refetchKeys } = useQuery( @@ -1524,7 +1530,7 @@ export function IntegrationsView() { const data = (await res.json()) as { keys?: ListedApiKey[] } return data.keys ?? [] }, - enabled: !!org?.id, + enabled: !publicMode && !!org?.id, staleTime: 30 * 1000, }, ) @@ -1678,7 +1684,15 @@ export function IntegrationsView() { } } - const availablePluginIds = pluginsData?.plugins ?? Object.keys(PLUGIN_CATALOG) + const redirectToLogin = useCallback(() => { + const loginUrl = new URL("/login", window.location.origin) + loginUrl.searchParams.set("redirect", window.location.href) + window.location.assign(loginUrl.toString()) + }, []) + + const availablePluginIds = publicMode + ? Object.keys(PLUGIN_CATALOG) + : (pluginsData?.plugins ?? Object.keys(PLUGIN_CATALOG)) const enabledPluginIds = new Set( availablePluginIds.filter((id) => PLUGIN_CATALOG[id]), ) @@ -1714,6 +1728,7 @@ export function IntegrationsView() { const isItemConnected = useCallback( (item: Item): boolean => { + if (publicMode) return false if (item.kind === "plugin") { return activePluginById.has(item.pluginId) } @@ -1722,7 +1737,7 @@ export function IntegrationsView() { } return false }, - [activePluginById, connectionsByProvider], + [activePluginById, connectionsByProvider, publicMode], ) const counts = useMemo>( @@ -1780,6 +1795,10 @@ export function IntegrationsView() { ), ctaLabel: "Connect", onCta: () => { + if (publicMode) { + redirectToLogin() + return + } window.open(POKE_RECIPE_URL, "_blank", "noopener,noreferrer") }, }, @@ -1802,6 +1821,10 @@ export function IntegrationsView() { docsUrl: "https://supermemory.ai/docs/supermemory-mcp/introduction", ctaLabel: "Connect", onCta: () => { + if (publicMode) { + redirectToLogin() + return + } void setMcpClient(null) setViewMode("mcp") }, @@ -1831,12 +1854,18 @@ export function IntegrationsView() { /> ), docsUrl: "https://supermemory.ai/docs/integrations/claude-code", - ctaLabel: claudeCodeConnected - ? "Active" - : claudeCodeNeedsPro - ? "Upgrade" - : "Connect", + ctaLabel: publicMode + ? "Connect" + : claudeCodeConnected + ? "Active" + : claudeCodeNeedsPro + ? "Upgrade" + : "Connect", onCta: () => { + if (publicMode) { + redirectToLogin() + return + } if (claudeCodeConnected) return if (claudeCodeNeedsPro) { handleUpgrade() @@ -1855,6 +1884,10 @@ export function IntegrationsView() { backdrop: , ctaLabel: "Connect", onCta: () => { + if (publicMode) { + redirectToLogin() + return + } window.open(CHROME_EXTENSION_URL, "_blank", "noopener,noreferrer") analytics.onboardingChromeExtensionClicked({ source: "integrations" }) }, @@ -1885,6 +1918,19 @@ export function IntegrationsView() { }) const renderRight = (item: Item): ReactNode => { + if (publicMode) { + return ( + { + trackCard(item) + redirectToLogin() + }} + > + Connect + + ) + } + switch (item.kind) { case "plugin": { const activeKey = activePluginById.get(item.pluginId) @@ -2065,6 +2111,8 @@ export function IntegrationsView() { } const renderStatus = (item: Item): ReactNode => { + if (publicMode) return null + switch (item.kind) { case "plugin": { const activeKey = activePluginById.get(item.pluginId) diff --git a/apps/web/components/memory-graph/graph-card.tsx b/apps/web/components/memory-graph/graph-card.tsx index 0269aab8d..de8bed3d2 100644 --- a/apps/web/components/memory-graph/graph-card.tsx +++ b/apps/web/components/memory-graph/graph-card.tsx @@ -4,6 +4,7 @@ import { memo, useMemo } from "react" import { cn } from "@lib/utils" import { dmSansClassName } from "@/lib/fonts" import { Expand } from "lucide-react" +import { SuperLoader } from "@/components/superloader" import { useGraphApi } from "./hooks/use-graph-api" import { useViewMode } from "@/lib/view-mode-context" @@ -157,7 +158,12 @@ export const GraphCard = memo(
{isLoading ? (
-
+
) : documentCount > 0 || memoryCount > 0 ? ( loadMore() : undefined} + isLoading={false} + isLoadingMore={false} + onLoadMore={hasMore && !isLoadingMore ? () => loadMore() : undefined} hasMore={hasMore} error={externalError || apiError} variant={variant} @@ -70,6 +72,16 @@ export function MemoryGraph({ > {children} + {isInitialLoading && ( +
+ +
+ )}
) } diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 4715c5710..2094e5aa3 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -31,6 +31,11 @@ export default async function proxy(request: Request) { return NextResponse.next() } + // Integrations index is public in guest mode; actions still require login. + if (url.pathname === "/" && url.searchParams.get("view") === "integrations") { + return NextResponse.next() + } + if (url.pathname.startsWith("/api/")) { if (!sessionCookie) { console.debug("[MIDDLEWARE] API route without session, returning 401") diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index ab721b399..01439d1e6 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -7,7 +7,7 @@ const ADD_MEMORY_SHORTCUT_URL = const RAYCAST_EXTENSION_URL = "https://www.raycast.com/supermemory/supermemory" const CHROME_EXTENSION_URL = "https://chromewebstore.google.com/detail/supermemory/afpgkkipfdpeaflnpoaffkcankadgjfc" -const POKE_RECIPE_URL = "https://poke.com/r/5tHPbS8gZvA" +const POKE_RECIPE_URL = "https://supermemory.link/poke" export { BIG_DIMENSIONS_NEW,