diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 680d208a..8bfa0de6 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,939 +1,751 @@ -'use client'; +"use client"; -import { useIsMobile } from '@hooks/use-mobile'; -import { useAuth } from '@lib/auth-context'; -import { $fetch } from '@repo/lib/api'; -import { MemoryGraph } from '@repo/ui/memory-graph'; -import type { DocumentsWithMemoriesResponseSchema } from '@repo/validation/api'; -import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; -import { Logo, LogoFull } from '@ui/assets/Logo'; -import { GlassMenuEffect } from '@ui/other/glass-effect'; -import { Button } from '@ui/components/button'; +import { useIsMobile } from "@hooks/use-mobile"; +import { useAuth } from "@lib/auth-context"; +import { $fetch } from "@repo/lib/api"; +import { MemoryGraph } from "@repo/ui/memory-graph"; +import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api"; +import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; +import { Logo, LogoFull } from "@ui/assets/Logo"; +import { Button } from "@ui/components/button"; +import { GlassMenuEffect } from "@ui/other/glass-effect"; import { - Gift, - LayoutGrid, - List, - LoaderIcon, - MessageSquare, - Unplug, -} from 'lucide-react'; -import { AnimatePresence, motion } from 'motion/react'; -import Link from 'next/link'; -import { useRouter, useSearchParams } from 'next/navigation'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import type { z } from 'zod'; -import { MemoryListView } from '@/components/memory-list-view'; -import Menu from '@/components/menu'; -import type { TourStep } from '@/components/tour'; -import { TourAlertDialog, useTour } from '@/components/tour'; -import { useProject } from '@/stores'; -import { TOUR_STEP_IDS, TOUR_STORAGE_KEY } from '@/lib/tour-constants'; -import { useViewMode } from '@/lib/view-mode-context'; -import { useChatOpen } from '@/stores'; -import { ChatRewrite } from '@/components/views/chat'; -import { useGraphHighlights } from '@/stores/highlights'; -import { ProjectSelector } from '@/components/project-selector'; -import { AddMemoryView } from '@/components/views/add-memory'; -import { ReferralUpgradeModal } from '@/components/referral-upgrade-modal'; -import { ConnectAIModal } from '@/components/connect-ai-modal'; -import { InstallPrompt } from '@/components/install-prompt'; + LayoutGrid, + List, + LoaderIcon, + MessageSquare, + Unplug, +} from "lucide-react"; +import { AnimatePresence, motion } from "motion/react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import type { z } from "zod"; +import { ConnectAIModal } from "@/components/connect-ai-modal"; +import { InstallPrompt } from "@/components/install-prompt"; +import { MemoryListView } from "@/components/memory-list-view"; +import Menu from "@/components/menu"; +import { ProjectSelector } from "@/components/project-selector"; +import { ReferralUpgradeModal } from "@/components/referral-upgrade-modal"; +import type { TourStep } from "@/components/tour"; +import { TourAlertDialog, useTour } from "@/components/tour"; +import { AddMemoryView } from "@/components/views/add-memory"; +import { ChatRewrite } from "@/components/views/chat"; +import { TOUR_STEP_IDS, TOUR_STORAGE_KEY } from "@/lib/tour-constants"; +import { useViewMode } from "@/lib/view-mode-context"; +import { useChatOpen, useProject } from "@/stores"; +import { useGraphHighlights } from "@/stores/highlights"; type DocumentsResponse = z.infer; -type DocumentWithMemories = DocumentsResponse['documents'][0]; +type DocumentWithMemories = DocumentsResponse["documents"][0]; + +interface ProjectMeta { + containerTag: string; + isExperimental?: boolean; +} + +interface DocumentWithCustomId extends DocumentWithMemories { + customId?: string; +} + +interface ApiResponse { + error?: { message?: string } | null; + data?: { + documents?: DocumentWithMemories[]; + }; +} const MemoryGraphPage = () => { - const { documentIds: allHighlightDocumentIds } = useGraphHighlights(); - const isMobile = useIsMobile(); - const { viewMode, setViewMode, isInitialized } = useViewMode(); - const { selectedProject } = useProject(); - const { setSteps, isTourCompleted } = useTour(); - const { isOpen, setIsOpen } = useChatOpen(); - const [injectedDocs, setInjectedDocs] = useState([]); - const [showAddMemoryView, setShowAddMemoryView] = useState(false); - const [showReferralModal, setShowReferralModal] = useState(false); + const { documentIds: allHighlightDocumentIds } = useGraphHighlights(); + const isMobile = useIsMobile(); + const { viewMode, setViewMode } = useViewMode(); + const { selectedProject } = useProject(); + const { setSteps, isTourCompleted } = useTour(); + const { isOpen, setIsOpen } = useChatOpen(); + const [injectedDocs, setInjectedDocs] = useState([]); + const [showAddMemoryView, setShowAddMemoryView] = useState(false); + const [showReferralModal, setShowReferralModal] = useState(false); - // Fetch projects meta to detect experimental flag - const { data: projectsMeta = [] } = useQuery({ - queryKey: ['projects'], - queryFn: async () => { - const response = await $fetch('@get/projects'); - return response.data?.projects ?? []; - }, - staleTime: 5 * 60 * 1000, - }); + // Fetch projects meta to detect experimental flag + const { data: projectsMeta = [] } = useQuery({ + queryKey: ["projects"], + queryFn: async () => { + const response = await $fetch("@get/projects"); + return response.data?.projects ?? []; + }, + staleTime: 5 * 60 * 1000, + }); - const isCurrentProjectExperimental = !!projectsMeta.find( - (p: any) => p.containerTag === selectedProject - )?.isExperimental; + const isCurrentProjectExperimental = !!projectsMeta.find( + (p: ProjectMeta) => p.containerTag === selectedProject, + )?.isExperimental; - // Tour state - const [showTourDialog, setShowTourDialog] = useState(false); + // Tour state + const [showTourDialog, setShowTourDialog] = useState(false); - // Define tour steps with useMemo to prevent recreation - const tourSteps: TourStep[] = useMemo(() => { - return [ - { - content: ( -
-

- Memories Overview -

-

- This is your memory graph. Each node represents a memory, and - connections show relationships between them. -

-
- ), - selectorId: TOUR_STEP_IDS.MEMORY_GRAPH, - position: 'center', - }, - { - content: ( -
-

- Add Memories -

-

- Click here to add new memories to your knowledge base. You can add - text, links, or connect external sources. -

-
- ), - selectorId: TOUR_STEP_IDS.MENU_ADD_MEMORY, - position: 'right', - }, - { - content: ( -
-

- Connections -

-

- Connect your external accounts like Google Drive, Notion, or - OneDrive to automatically sync and organize your content. -

-
- ), - selectorId: TOUR_STEP_IDS.MENU_CONNECTIONS, - position: 'right', - }, - { - content: ( -
-

Projects

-

- Organize your memories into projects. Switch between different - contexts easily. -

-
- ), - selectorId: TOUR_STEP_IDS.MENU_PROJECTS, - position: 'right', - }, - { - content: ( -
-

- MCP Servers -

-

- Access Model Context Protocol servers to give AI tools access to - your memories securely. -

-
- ), - selectorId: TOUR_STEP_IDS.MENU_MCP, - position: 'right', - }, - { - content: ( -
-

Billing

-

- Manage your subscription and billing information. -

-
- ), - selectorId: TOUR_STEP_IDS.MENU_BILLING, - position: 'right', - }, - { - content: ( -
-

- View Toggle -

-

- Switch between graph view and list view to see your memories in - different ways. -

-
- ), - selectorId: TOUR_STEP_IDS.VIEW_TOGGLE, - position: 'left', - }, - { - content: ( -
-

Legend

-

- Understand the different types of nodes and connections in your - memory graph. -

-
- ), - selectorId: TOUR_STEP_IDS.LEGEND, - position: 'left', - }, - { - content: ( -
-

- Chat Assistant -

-

- Ask questions or add new memories using our AI-powered chat - interface. -

-
- ), - selectorId: TOUR_STEP_IDS.FLOATING_CHAT, - position: 'left', - }, - ]; - }, []); + // Define tour steps with useMemo to prevent recreation + const tourSteps: TourStep[] = useMemo(() => { + return [ + { + content: ( +
+

+ Memories Overview +

+

+ This is your memory graph. Each node represents a memory, and + connections show relationships between them. +

+
+ ), + selectorId: TOUR_STEP_IDS.MEMORY_GRAPH, + position: "center", + }, + { + content: ( +
+

+ Add Memories +

+

+ Click here to add new memories to your knowledge base. You can add + text, links, or connect external sources. +

+
+ ), + selectorId: TOUR_STEP_IDS.MENU_ADD_MEMORY, + position: "right", + }, + { + content: ( +
+

+ Connections +

+

+ Connect your external accounts like Google Drive, Notion, or + OneDrive to automatically sync and organize your content. +

+
+ ), + selectorId: TOUR_STEP_IDS.MENU_CONNECTIONS, + position: "right", + }, + { + content: ( +
+

Projects

+

+ Organize your memories into projects. Switch between different + contexts easily. +

+
+ ), + selectorId: TOUR_STEP_IDS.MENU_PROJECTS, + position: "right", + }, + { + content: ( +
+

+ MCP Servers +

+

+ Access Model Context Protocol servers to give AI tools access to + your memories securely. +

+
+ ), + selectorId: TOUR_STEP_IDS.MENU_MCP, + position: "right", + }, + { + content: ( +
+

Billing

+

+ Manage your subscription and billing information. +

+
+ ), + selectorId: TOUR_STEP_IDS.MENU_BILLING, + position: "right", + }, + { + content: ( +
+

+ View Toggle +

+

+ Switch between graph view and list view to see your memories in + different ways. +

+
+ ), + selectorId: TOUR_STEP_IDS.VIEW_TOGGLE, + position: "left", + }, + { + content: ( +
+

Legend

+

+ Understand the different types of nodes and connections in your + memory graph. +

+
+ ), + selectorId: TOUR_STEP_IDS.LEGEND, + position: "left", + }, + { + content: ( +
+

+ Chat Assistant +

+

+ Ask questions or add new memories using our AI-powered chat + interface. +

+
+ ), + selectorId: TOUR_STEP_IDS.FLOATING_CHAT, + position: "left", + }, + ]; + }, []); - // Check if tour has been completed before - useEffect(() => { - const hasCompletedTour = localStorage.getItem(TOUR_STORAGE_KEY) === 'true'; - if (!hasCompletedTour && !isTourCompleted) { - const timer = setTimeout(() => { - setShowTourDialog(true); - }, 1000); // Show after 1 second - return () => clearTimeout(timer); - } - }, [isTourCompleted]); + // Check if tour has been completed before + useEffect(() => { + const hasCompletedTour = localStorage.getItem(TOUR_STORAGE_KEY) === "true"; + if (!hasCompletedTour && !isTourCompleted) { + const timer = setTimeout(() => { + setShowTourDialog(true); + }, 1000); // Show after 1 second + return () => clearTimeout(timer); + } + }, [isTourCompleted]); - // Set up tour steps - useEffect(() => { - setSteps(tourSteps); - }, [setSteps, tourSteps]); + // Set up tour steps + useEffect(() => { + setSteps(tourSteps); + }, [setSteps, tourSteps]); - // Save tour completion to localStorage - useEffect(() => { - if (isTourCompleted) { - localStorage.setItem(TOUR_STORAGE_KEY, 'true'); - } - }, [isTourCompleted]); + // Save tour completion to localStorage + useEffect(() => { + if (isTourCompleted) { + localStorage.setItem(TOUR_STORAGE_KEY, "true"); + } + }, [isTourCompleted]); - // Progressive loading via useInfiniteQuery - const IS_DEV = process.env.NODE_ENV === 'development'; - const PAGE_SIZE = IS_DEV ? 3 : 100; - const MAX_TOTAL = 1000; + // Progressive loading via useInfiniteQuery + const IS_DEV = process.env.NODE_ENV === "development"; + const PAGE_SIZE = IS_DEV ? 3 : 100; + const MAX_TOTAL = 1000; - const { - data, - error, - isPending, - isFetchingNextPage, - hasNextPage, - fetchNextPage, - } = useInfiniteQuery({ - queryKey: ['documents-with-memories', selectedProject], - initialPageParam: 1, - queryFn: async ({ pageParam }) => { - const response = await $fetch('@post/memories/documents', { - body: { - page: pageParam as number, - limit: (pageParam as number) === 1 ? (IS_DEV ? 3 : 500) : PAGE_SIZE, - sort: 'createdAt', - order: 'desc', - containerTags: selectedProject ? [selectedProject] : undefined, - }, - disableValidation: true, - }); + const { + data, + error, + isPending, + isFetchingNextPage, + hasNextPage, + fetchNextPage, + } = useInfiniteQuery({ + queryKey: ["documents-with-memories", selectedProject], + initialPageParam: 1, + queryFn: async ({ pageParam }) => { + const response = await $fetch("@post/memories/documents", { + body: { + page: pageParam as number, + limit: (pageParam as number) === 1 ? (IS_DEV ? 3 : 500) : PAGE_SIZE, + sort: "createdAt", + order: "desc", + containerTags: selectedProject ? [selectedProject] : undefined, + }, + disableValidation: true, + }); - if (response.error) { - throw new Error(response.error?.message || 'Failed to fetch documents'); - } + if (response.error) { + throw new Error(response.error?.message || "Failed to fetch documents"); + } - return response.data; - }, - getNextPageParam: (lastPage, allPages) => { - const loaded = allPages.reduce( - (acc, p) => acc + (p.documents?.length ?? 0), - 0 - ); - if (loaded >= MAX_TOTAL) return undefined; + return response.data; + }, + getNextPageParam: (lastPage, allPages) => { + const loaded = allPages.reduce( + (acc, p) => acc + (p.documents?.length ?? 0), + 0, + ); + if (loaded >= MAX_TOTAL) return undefined; - const { currentPage, totalPages } = lastPage.pagination; - if (currentPage < totalPages) { - return currentPage + 1; - } - return undefined; - }, - staleTime: 5 * 60 * 1000, - }); + const { currentPage, totalPages } = lastPage.pagination; + if (currentPage < totalPages) { + return currentPage + 1; + } + return undefined; + }, + staleTime: 5 * 60 * 1000, + }); - const baseDocuments = useMemo(() => { - return ( - data?.pages.flatMap((p: DocumentsResponse) => p.documents ?? []) ?? [] - ); - }, [data]); + const baseDocuments = useMemo(() => { + return ( + data?.pages.flatMap((p: DocumentsResponse) => p.documents ?? []) ?? [] + ); + }, [data]); - const allDocuments = useMemo(() => { - if (injectedDocs.length === 0) return baseDocuments; - const byId = new Map(); - for (const d of injectedDocs) byId.set(d.id, d); - for (const d of baseDocuments) if (!byId.has(d.id)) byId.set(d.id, d); - return Array.from(byId.values()); - }, [baseDocuments, injectedDocs]); + const allDocuments = useMemo(() => { + if (injectedDocs.length === 0) return baseDocuments; + const byId = new Map(); + for (const d of injectedDocs) byId.set(d.id, d); + for (const d of baseDocuments) if (!byId.has(d.id)) byId.set(d.id, d); + return Array.from(byId.values()); + }, [baseDocuments, injectedDocs]); - const totalLoaded = allDocuments.length; - const hasMore = hasNextPage; - const isLoadingMore = isFetchingNextPage; + const totalLoaded = allDocuments.length; + const hasMore = hasNextPage; + const isLoadingMore = isFetchingNextPage; - const loadMoreDocuments = useCallback(async (): Promise => { - if (hasNextPage && !isFetchingNextPage) { - await fetchNextPage(); - return; - } - return; - }, [hasNextPage, isFetchingNextPage, fetchNextPage]); + const loadMoreDocuments = useCallback(async (): Promise => { + if (hasNextPage && !isFetchingNextPage) { + await fetchNextPage(); + return; + } + return; + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); - // Reset injected docs when project changes - useEffect(() => { - setInjectedDocs([]); - }, [selectedProject]); + // Reset injected docs when project changes + useEffect(() => { + setInjectedDocs([]); + }, []); - // Surgical fetch of missing highlighted documents (customId-based IDs from search) - useEffect(() => { - if (!isOpen) return; - if (!allHighlightDocumentIds || allHighlightDocumentIds.length === 0) - return; - const present = new Set(); - for (const d of [...baseDocuments, ...injectedDocs]) { - if (d.id) present.add(d.id); - if ((d as any).customId) present.add((d as any).customId as string); - } - const missing = allHighlightDocumentIds.filter( - (id: string) => !present.has(id) - ); - if (missing.length === 0) return; - let cancelled = false; - const run = async () => { - try { - const resp = await $fetch('@post/memories/documents/by-ids', { - body: { - ids: missing, - by: 'customId', - containerTags: selectedProject ? [selectedProject] : undefined, - }, - disableValidation: true, - }); - if (cancelled || (resp as any)?.error) return; - const extraDocs = (resp as any)?.data?.documents as - | DocumentWithMemories[] - | undefined; - if (!extraDocs || extraDocs.length === 0) return; - setInjectedDocs((prev) => { - const seen = new Set([ - ...prev.map((d) => d.id), - ...baseDocuments.map((d) => d.id), - ]); - const merged = [...prev]; - for (const doc of extraDocs) { - if (!seen.has(doc.id)) { - merged.push(doc); - seen.add(doc.id); - } - } - return merged; - }); - } catch {} - }; - void run(); - return () => { - cancelled = true; - }; - }, [ - isOpen, - allHighlightDocumentIds.join('|'), - baseDocuments, - injectedDocs, - selectedProject, - $fetch, - ]); + // Surgical fetch of missing highlighted documents (customId-based IDs from search) + useEffect(() => { + if (!isOpen) return; + if (!allHighlightDocumentIds || allHighlightDocumentIds.length === 0) + return; + const present = new Set(); + for (const d of [...baseDocuments, ...injectedDocs]) { + if (d.id) present.add(d.id); + const docWithCustomId = d as DocumentWithCustomId; + if (docWithCustomId.customId) present.add(docWithCustomId.customId); + } + const missing = allHighlightDocumentIds.filter( + (id: string) => !present.has(id), + ); + if (missing.length === 0) return; + let cancelled = false; + const run = async () => { + try { + const resp = await $fetch("@post/memories/documents/by-ids", { + body: { + ids: missing, + by: "customId", + containerTags: selectedProject ? [selectedProject] : undefined, + }, + disableValidation: true, + }); + const apiResp = resp as unknown as ApiResponse; + if (cancelled || apiResp?.error) return; + const extraDocs = apiResp?.data?.documents; + if (!extraDocs || extraDocs.length === 0) return; + setInjectedDocs((prev) => { + const seen = new Set([ + ...prev.map((d) => d.id), + ...baseDocuments.map((d) => d.id), + ]); + const merged = [...prev]; + for (const doc of extraDocs) { + if (!seen.has(doc.id)) { + merged.push(doc); + seen.add(doc.id); + } + } + return merged; + }); + } catch { + // Ignore errors + } + }; + void run(); + return () => { + cancelled = true; + }; + }, [ + isOpen, + baseDocuments, + injectedDocs, + selectedProject, + allHighlightDocumentIds, + ]); - // Handle view mode change - const handleViewModeChange = useCallback( - (mode: 'graph' | 'list') => { - setViewMode(mode); - }, - [setViewMode] - ); + // Handle view mode change + const handleViewModeChange = useCallback( + (mode: "graph" | "list") => { + setViewMode(mode); + }, + [setViewMode], + ); - // Prevent body scrolling - useEffect(() => { - document.body.style.overflow = 'hidden'; - document.body.style.height = '100vh'; - document.documentElement.style.overflow = 'hidden'; - document.documentElement.style.height = '100vh'; + // Prevent body scrolling + useEffect(() => { + document.body.style.overflow = "hidden"; + document.body.style.height = "100vh"; + document.documentElement.style.overflow = "hidden"; + document.documentElement.style.height = "100vh"; - return () => { - document.body.style.overflow = ''; - document.body.style.height = ''; - document.documentElement.style.overflow = ''; - document.documentElement.style.height = ''; - }; - }, []); + return () => { + document.body.style.overflow = ""; + document.body.style.height = ""; + document.documentElement.style.overflow = ""; + document.documentElement.style.height = ""; + }; + }, []); - return ( -
- {/* Main content area */} - - - -
- handleViewModeChange('graph')} - transition={{ duration: 0.2 }} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} - > - {viewMode === 'graph' && ( - - )} - - - Graph - - + return ( +
+ {/* Main content area */} + + + +
+ handleViewModeChange("graph")} + transition={{ duration: 0.2 }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + {viewMode === "graph" && ( + + )} + + + Graph + + - handleViewModeChange('list')} - transition={{ duration: 0.2 }} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} - > - {viewMode === 'list' && ( - - )} - - - List - - -
-
- handleViewModeChange('list')} - transition={{ duration: 0.2 }} - whileHover={{ scale: 1.02 }} - whileTap={{ scale: 0.98 }} - > - {viewMode === 'list' && ( - - )} - - - List - - -
- + handleViewModeChange("list")} + transition={{ duration: 0.2 }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + {viewMode === "list" && ( + + )} + + + List + + +
+
- {/* Animated content switching */} - - {viewMode === 'graph' ? ( - - -
-
-
-

- No Memories to Visualize -

- -
-
-
-
-
- ) : ( - - -
-
-
-

- No Memories to Visualize -

- -
-
-
-
-
- )} -
- {/* Animated content switching */} - - {viewMode === 'graph' ? ( - - -
-
-
-

- No Memories to Visualize -

- -
-
-
-
-
- ) : ( - - -
-
-
-

- No Memories to Visualize -

- -
-
-
-
-
- )} -
+ {/* Animated content switching */} + + {viewMode === "graph" ? ( + + +
+
+
+

+ No Memories to Visualize +

+ +
+
+
+
+
+ ) : ( + + +
+
+
+

+ No Memories to Visualize +

+ +
+
+
+
+
+ )} +
- {/* Top Bar */} -
-
- - - - - {/* Top Bar */} -
-
- - - - + {/* Top Bar */} +
+
+ + + + -
- -
-
- -
+
+ +
- - - -
- - - -
+ + + +
-
- -
-
-
- -
-
+
+ +
+
- {/* Floating Open Chat Button */} - {!isOpen && !isMobile && ( - - - - )} -
+ {/* Floating Open Chat Button */} + {!isOpen && !isMobile && ( + + + + )} + - {/* Chat panel - positioned absolutely */} - - - - - - {/* Chat panel - positioned absolutely */} - - - - - + {/* Chat panel - positioned absolutely */} + + + + + - {showAddMemoryView && ( - setShowAddMemoryView(false)} - /> - )} - {showAddMemoryView && ( - setShowAddMemoryView(false)} - /> - )} + {showAddMemoryView && ( + setShowAddMemoryView(false)} + /> + )} - {/* Tour Alert Dialog */} - - {/* Tour Alert Dialog */} - + {/* Tour Alert Dialog */} + - {/* Referral/Upgrade Modal */} - setShowReferralModal(false)} - /> -
- ); -}; - {/* Referral/Upgrade Modal */} - setShowReferralModal(false)} - /> - - ); + {/* Referral/Upgrade Modal */} + setShowReferralModal(false)} + /> + + ); }; // Wrapper component to handle auth and waitlist checks export default function Page() { - const router = useRouter(); - const searchParams = useSearchParams(); - const { user } = useAuth(); + const router = useRouter(); + const { user } = useAuth(); - useEffect(() => { - // Get the raw token from URL without URL decoding - const url = new URL(window.location.href); - const rawToken = url.searchParams.get('token'); + useEffect(() => { + // Get the raw token from URL without URL decoding + const url = new URL(window.location.href); + const rawToken = url.searchParams.get("token"); - if (rawToken) { - // Re-encode the token to preserve the original encoding - const encodedToken = encodeURIComponent(rawToken); - console.log('Token extracted:', encodedToken); + if (rawToken) { + // Re-encode the token to preserve the original encoding + const encodedToken = encodeURIComponent(rawToken); + console.log("Token extracted:", encodedToken); - window.postMessage({ token: encodedToken }, '*'); - url.searchParams.delete('token'); - window.history.replaceState({}, '', url.toString()); - } - }, [searchParams]); + window.postMessage({ token: encodedToken }, "*"); + url.searchParams.delete("token"); + window.history.replaceState({}, "", url.toString()); + } + }, []); - // Check waitlist status - const { - data: waitlistStatus, - isLoading: isCheckingWaitlist, - error: waitlistError, - } = useQuery({ - queryKey: ['waitlist-status', user?.id], - queryFn: async () => { - try { - const response = await $fetch('@get/waitlist/status'); - return response.data; - } catch (error) { - console.error('Error checking waitlist status:', error); - // Return null to indicate error, will handle in useEffect - return null; - } - }, - enabled: !!user && !user.isAnonymous, - staleTime: 5 * 60 * 1000, // 5 minutes - retry: 1, // Only retry once on failure - }); + // Check waitlist status + const { data: waitlistStatus, isLoading: isCheckingWaitlist } = useQuery({ + queryKey: ["waitlist-status", user?.id], + queryFn: async () => { + try { + const response = await $fetch("@get/waitlist/status"); + return response.data; + } catch (error) { + console.error("Error checking waitlist status:", error); + // Return null to indicate error, will handle in useEffect + return null; + } + }, + enabled: !!user && !user.isAnonymous, + staleTime: 5 * 60 * 1000, // 5 minutes + retry: 1, // Only retry once on failure + }); - useEffect(() => { - if (waitlistStatus && !waitlistStatus.accessGranted) { - router.push('/waitlist'); - } - }, []); + useEffect(() => { + if (waitlistStatus && !waitlistStatus.accessGranted) { + router.push("/waitlist"); + } + }, [waitlistStatus, router]); - // Show loading state while checking authentication and waitlist status - if (!user || isCheckingWaitlist) { - return ( -
-
- -

Loading...

-
-
- ); - } + // Show loading state while checking authentication and waitlist status + if (!user || isCheckingWaitlist) { + return ( +
+
+ +

Loading...

+
+
+ ); + } - // If we have a user and they have access, show the main component - return ( - <> - - - - ); + // If we have a user and they have access, show the main component + return ( + <> + + + + ); }