diff options
| author | Mahesh Sanikommmu <[email protected]> | 2025-08-16 18:50:10 -0700 |
|---|---|---|
| committer | Mahesh Sanikommmu <[email protected]> | 2025-08-16 18:50:10 -0700 |
| commit | 39003aff23d64ff1d96074d71521f6023c9bec01 (patch) | |
| tree | 3f870c04b3dce315bba1b21aa2da158494e71774 /apps/web/lib | |
| parent | Merge pull request #355 from supermemoryai/archive (diff) | |
| download | supermemory-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.ts | 44 | ||||
| -rw-r--r-- | apps/web/lib/mobile-panel-context.tsx | 32 | ||||
| -rw-r--r-- | apps/web/lib/tour-constants.ts | 23 | ||||
| -rw-r--r-- | apps/web/lib/view-mode-context.tsx | 96 |
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 +} |