aboutsummaryrefslogtreecommitdiff
path: root/apps/web/lib
diff options
context:
space:
mode:
authorMahesh Sanikommmu <[email protected]>2025-08-16 18:50:10 -0700
committerMahesh Sanikommmu <[email protected]>2025-08-16 18:50:10 -0700
commit39003aff23d64ff1d96074d71521f6023c9bec01 (patch)
tree3f870c04b3dce315bba1b21aa2da158494e71774 /apps/web/lib
parentMerge pull request #355 from supermemoryai/archive (diff)
downloadsupermemory-39003aff23d64ff1d96074d71521f6023c9bec01.tar.xz
supermemory-39003aff23d64ff1d96074d71521f6023c9bec01.zip
New Version of Supermemory Consumer App
Diffstat (limited to 'apps/web/lib')
-rw-r--r--apps/web/lib/analytics.ts44
-rw-r--r--apps/web/lib/mobile-panel-context.tsx32
-rw-r--r--apps/web/lib/tour-constants.ts23
-rw-r--r--apps/web/lib/view-mode-context.tsx96
4 files changed, 195 insertions, 0 deletions
diff --git a/apps/web/lib/analytics.ts b/apps/web/lib/analytics.ts
new file mode 100644
index 00000000..21fec3ea
--- /dev/null
+++ b/apps/web/lib/analytics.ts
@@ -0,0 +1,44 @@
+import posthog from "posthog-js"
+
+export const analytics = {
+ userSignedOut: () => posthog.capture("user_signed_out"),
+ tourStarted: () => posthog.capture("tour_started"),
+ tourCompleted: () => posthog.capture("tour_completed"),
+ tourSkipped: () => posthog.capture("tour_skipped"),
+
+ memoryAdded: (props: {
+ type: "note" | "link" | "file"
+ project_id?: string
+ content_length?: number
+ file_size?: number
+ file_type?: string
+ }) => posthog.capture("memory_added", props),
+
+ memoryDetailOpened: () => posthog.capture("memory_detail_opened"),
+
+ projectCreated: () => posthog.capture("project_created"),
+
+ newChatStarted: () => posthog.capture("new_chat_started"),
+ chatHistoryViewed: () => posthog.capture("chat_history_viewed"),
+ chatDeleted: () => posthog.capture("chat_deleted"),
+
+ viewModeChanged: (mode: "graph" | "list") =>
+ posthog.capture("view_mode_changed", { mode }),
+
+ documentCardClicked: () => posthog.capture("document_card_clicked"),
+
+ billingViewed: () => posthog.capture("billing_viewed"),
+ upgradeInitiated: () => posthog.capture("upgrade_initiated"),
+ upgradeCompleted: () => posthog.capture("upgrade_completed"),
+ billingPortalOpened: () => posthog.capture("billing_portal_opened"),
+
+ connectionAdded: (provider: string) =>
+ posthog.capture("connection_added", { provider }),
+ connectionDeleted: () => posthog.capture("connection_deleted"),
+ connectionAuthStarted: () => posthog.capture("connection_auth_started"),
+ connectionAuthCompleted: () => posthog.capture("connection_auth_completed"),
+ connectionAuthFailed: () => posthog.capture("connection_auth_failed"),
+
+ mcpViewOpened: () => posthog.capture("mcp_view_opened"),
+ mcpInstallCmdCopied: () => posthog.capture("mcp_install_cmd_copied"),
+} \ No newline at end of file
diff --git a/apps/web/lib/mobile-panel-context.tsx b/apps/web/lib/mobile-panel-context.tsx
new file mode 100644
index 00000000..3b0b1838
--- /dev/null
+++ b/apps/web/lib/mobile-panel-context.tsx
@@ -0,0 +1,32 @@
+"use client"
+
+import { createContext, type ReactNode, useContext, useState } from "react"
+
+type ActivePanel = "menu" | "chat" | null
+
+interface MobilePanelContextType {
+ activePanel: ActivePanel
+ setActivePanel: (panel: ActivePanel) => void
+}
+
+const MobilePanelContext = createContext<MobilePanelContextType | undefined>(
+ undefined,
+)
+
+export function MobilePanelProvider({ children }: { children: ReactNode }) {
+ const [activePanel, setActivePanel] = useState<ActivePanel>(null)
+
+ return (
+ <MobilePanelContext.Provider value={{ activePanel, setActivePanel }}>
+ {children}
+ </MobilePanelContext.Provider>
+ )
+}
+
+export function useMobilePanel() {
+ const context = useContext(MobilePanelContext)
+ if (!context) {
+ throw new Error("useMobilePanel must be used within a MobilePanelProvider")
+ }
+ return context
+}
diff --git a/apps/web/lib/tour-constants.ts b/apps/web/lib/tour-constants.ts
new file mode 100644
index 00000000..b61fc1e7
--- /dev/null
+++ b/apps/web/lib/tour-constants.ts
@@ -0,0 +1,23 @@
+// Tour step IDs - these should match the IDs added to elements in your app
+export const TOUR_STEP_IDS = {
+ LOGO: "tour-logo",
+ MENU_BUTTON: "tour-menu-button",
+ VIEW_TOGGLE: "tour-view-toggle",
+ MEMORY_GRAPH: "tour-memory-graph",
+ MEMORY_LIST: "tour-memory-list",
+ FLOATING_CHAT: "tour-floating-chat",
+ ADD_MEMORY: "tour-add-memory",
+ SPACES_DROPDOWN: "tour-spaces-dropdown",
+ SETTINGS: "tour-settings",
+ MENU_CONNECTIONS: "tour-connections",
+ // Menu items
+ MENU_ADD_MEMORY: "tour-menu-add-memory",
+ MENU_PROJECTS: "tour-menu-projects",
+ MENU_MCP: "tour-menu-mcp",
+ MENU_BILLING: "tour-menu-billing",
+ // Legend
+ LEGEND: "tour-legend",
+} as const
+
+// Tour storage key for localStorage
+export const TOUR_STORAGE_KEY = "supermemory-tour-completed"
diff --git a/apps/web/lib/view-mode-context.tsx b/apps/web/lib/view-mode-context.tsx
new file mode 100644
index 00000000..87c11da1
--- /dev/null
+++ b/apps/web/lib/view-mode-context.tsx
@@ -0,0 +1,96 @@
+"use client"
+
+import {
+ createContext,
+ type ReactNode,
+ useContext,
+ useEffect,
+ useState,
+} from "react"
+import { analytics } from "@/lib/analytics"
+
+type ViewMode = "graph" | "list"
+
+interface ViewModeContextType {
+ viewMode: ViewMode
+ setViewMode: (mode: ViewMode) => void
+ isInitialized: boolean
+}
+
+const ViewModeContext = createContext<ViewModeContextType | undefined>(
+ undefined,
+)
+
+// Cookie utility functions
+const setCookie = (name: string, value: string, days = 365) => {
+ if (typeof document === "undefined") return
+ const expires = new Date()
+ expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000)
+ document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/`
+}
+
+const getCookie = (name: string): string | null => {
+ if (typeof document === "undefined") return null
+ const nameEQ = `${name}=`
+ const ca = document.cookie.split(";")
+ for (let i = 0; i < ca.length; i++) {
+ let c = ca[i]
+ if (!c) continue
+ while (c.charAt(0) === " ") c = c.substring(1, c.length)
+ if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length)
+ }
+ return null
+}
+
+const isMobileDevice = () => {
+ if (typeof window === "undefined") return false
+ return window.innerWidth < 768
+}
+
+export function ViewModeProvider({ children }: { children: ReactNode }) {
+ // Start with a default that works for SSR
+ const [viewMode, setViewModeState] = useState<ViewMode>("graph")
+ const [isInitialized, setIsInitialized] = useState(false)
+
+ // Load preferences on the client side
+ useEffect(() => {
+ if (!isInitialized) {
+ // Check for saved preference first
+ const savedMode = getCookie("memoryViewMode")
+ if (savedMode === "list" || savedMode === "graph") {
+ setViewModeState(savedMode)
+ } else {
+ // If no saved preference, default to list on mobile, graph on desktop
+ setViewModeState(isMobileDevice() ? "list" : "graph")
+ }
+ setIsInitialized(true)
+ }
+ }, [isInitialized])
+
+ // Save to cookie whenever view mode changes
+ const handleSetViewMode = (mode: ViewMode) => {
+ analytics.viewModeChanged(mode)
+ setViewModeState(mode)
+ setCookie("memoryViewMode", mode)
+ }
+
+ return (
+ <ViewModeContext.Provider
+ value={{
+ viewMode,
+ setViewMode: handleSetViewMode,
+ isInitialized,
+ }}
+ >
+ {children}
+ </ViewModeContext.Provider>
+ )
+}
+
+export function useViewMode() {
+ const context = useContext(ViewModeContext)
+ if (!context) {
+ throw new Error("useViewMode must be used within a ViewModeProvider")
+ }
+ return context
+}