diff --git a/web/app/(pages)/layout.tsx b/web/app/(pages)/layout.tsx
index fb32036d..8cadcceb 100644
--- a/web/app/(pages)/layout.tsx
+++ b/web/app/(pages)/layout.tsx
@@ -1,6 +1,6 @@
import { SignedInClient } from "@/components/custom/clerk/signed-in-client"
import { Sidebar } from "@/components/custom/sidebar/sidebar"
-import { PublicHomeRoute } from "@/components/routes/PublicHomeRoute"
+import { PublicHomeRoute } from "@/components/routes/public/PublicHomeRoute"
import { CommandPalette } from "@/components/ui/CommandPalette"
import { JazzClerkAuth, JazzProvider } from "@/lib/providers/jazz-provider"
import { currentUser } from "@clerk/nextjs/server"
diff --git a/web/components/routes/PublicHomeRoute.tsx b/web/components/routes/PublicHomeRoute.tsx
deleted file mode 100644
index 42739692..00000000
--- a/web/components/routes/PublicHomeRoute.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-"use client"
-import * as react from "react"
-import { useCoState } from "@/lib/providers/jazz-provider"
-import { PublicGlobalGroup } from "@/lib/schema/master/public-group"
-import dynamic from "next/dynamic"
-import Link from "next/link"
-import { JAZZ_GLOBAL_GROUP_ID } from "@/lib/constants"
-
-let graph_data_promise = import("./graph-data.json").then(a => a.default)
-const ForceGraphClient = dynamic(() => import("./force-graph-client-lazy"), { ssr: false })
-
-export function PublicHomeRoute() {
- let raw_graph_data = react.use(graph_data_promise)
-
- const [placeholder, setPlaceholder] = react.useState("Search something...")
- const [currentTopicIndex, setCurrentTopicIndex] = react.useState(0)
- const [currentCharIndex, setCurrentCharIndex] = react.useState(0)
- const globalGroup = useCoState(PublicGlobalGroup, JAZZ_GLOBAL_GROUP_ID, {
- root: {
- topics: []
- }
- })
- const topics = globalGroup?.root.topics?.map(topic => topic?.prettyName) || []
-
- react.useEffect(() => {
- if (topics.length === 0) return
-
- const typingInterval = setInterval(() => {
- const currentTopic = topics[currentTopicIndex]
- if (currentTopic && currentCharIndex < currentTopic.length) {
- setPlaceholder(`${currentTopic.slice(0, currentCharIndex + 1)}`)
- setCurrentCharIndex(currentCharIndex + 1)
- } else {
- clearInterval(typingInterval)
- setTimeout(() => {
- setCurrentTopicIndex(prevIndex => (prevIndex + 1) % topics.length)
- setCurrentCharIndex(0)
- }, 1000)
- }
- }, 200)
-
- return () => clearInterval(typingInterval)
- }, [currentTopicIndex, currentCharIndex, topics])
-
- return (
-
-
{
- console.log("clicked", val)
- }}
- filter_query=""
- />
-
-
Learn Anything
- Random Topic
-
-
-
-
i want to learn
-
-
-
-
- )
-}
diff --git a/web/components/routes/public/Autocomplete.tsx b/web/components/routes/public/Autocomplete.tsx
new file mode 100644
index 00000000..8b00b037
--- /dev/null
+++ b/web/components/routes/public/Autocomplete.tsx
@@ -0,0 +1,123 @@
+"use client"
+
+import React, { useState, useRef, useCallback, useMemo } from "react"
+import { Command, CommandGroup, CommandItem, CommandList } from "@/components/ui/command"
+import { Command as CommandPrimitive } from "cmdk"
+import { motion, AnimatePresence } from "framer-motion"
+import { cn } from "@/lib/utils"
+
+interface GraphNode {
+ name: string
+ prettyName: string
+ connectedTopics: string[]
+}
+
+interface AutocompleteProps {
+ topics: GraphNode[]
+ onSelect: (topic: GraphNode) => void
+ onInputChange: (value: string) => void
+}
+
+export function Autocomplete({ topics = [], onSelect, onInputChange }: AutocompleteProps): JSX.Element {
+ const inputRef = useRef(null)
+ const [open, setOpen] = useState(false)
+ const [inputValue, setInputValue] = useState("")
+
+ const filteredTopics = useMemo(() => {
+ if (!inputValue) {
+ return topics.slice(0, 5)
+ }
+ const regex = new RegExp(inputValue.split("").join(".*"), "i")
+ return topics.filter(
+ topic =>
+ regex.test(topic.name) ||
+ regex.test(topic.prettyName) ||
+ topic.connectedTopics.some(connectedTopic => regex.test(connectedTopic))
+ )
+ }, [inputValue, topics])
+
+ const handleSelect = useCallback(
+ (topic: GraphNode) => {
+ setInputValue(topic.prettyName)
+ setOpen(false)
+ onSelect(topic)
+ },
+ [onSelect]
+ )
+
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" && filteredTopics.length > 0) {
+ handleSelect(filteredTopics[0])
+ } else if ((e.key === "Backspace" || e.key === "Delete") && inputRef.current?.value === "") {
+ setOpen(true)
+ }
+ },
+ [filteredTopics, handleSelect]
+ )
+
+ const handleInputChange = useCallback(
+ (value: string) => {
+ setInputValue(value)
+ setOpen(true)
+ onInputChange(value)
+ },
+ [onInputChange]
+ )
+
+ return (
+
+
+ setTimeout(() => setOpen(false), 100)}
+ onFocus={() => setOpen(true)}
+ placeholder="Search for a topic..."
+ className={cn("placeholder:text-muted-foreground flex-1 bg-transparent px-2 py-1 outline-none", {
+ "mb-1 border-b pb-2.5": open
+ })}
+ />
+
+
+
+ {open && (
+
+
+
+ {filteredTopics.map(topic => (
+ handleSelect(topic)}
+ className="min-h-10 rounded-none px-3 py-1.5"
+ >
+ {topic.prettyName}
+
+ {topic.connectedTopics.length > 0 ? topic.connectedTopics.join(", ") : "-"}
+
+
+ ))}
+
+
+
+ )}
+
+
+
+ )
+}
+
+export default Autocomplete
diff --git a/web/components/routes/public/PublicHomeRoute.tsx b/web/components/routes/public/PublicHomeRoute.tsx
new file mode 100644
index 00000000..f6668b7a
--- /dev/null
+++ b/web/components/routes/public/PublicHomeRoute.tsx
@@ -0,0 +1,65 @@
+"use client"
+
+import * as React from "react"
+import dynamic from "next/dynamic"
+import { motion } from "framer-motion"
+import { Autocomplete } from "./Autocomplete"
+import { useRouter } from "next/navigation"
+
+let graph_data_promise = import("./graph-data.json").then(a => a.default)
+
+const ForceGraphClient = dynamic(() => import("./force-graph-client-lazy"), { ssr: false })
+
+interface GraphNode {
+ name: string
+ prettyName: string
+ connectedTopics: string[]
+}
+
+export function PublicHomeRoute() {
+ const router = useRouter()
+ const raw_graph_data = React.use(graph_data_promise) as GraphNode[]
+ const [filterQuery, setFilterQuery] = React.useState("")
+
+ const handleTopicSelect = (topicName: string) => {
+ router.push(`/${topicName}`)
+ }
+
+ const handleInputChange = (value: string) => {
+ setFilterQuery(value)
+ }
+
+ return (
+
+
handleTopicSelect(val)}
+ filter_query={filterQuery}
+ />
+
+
+
+ I want to learn
+
+ handleTopicSelect(topic.name)}
+ onInputChange={handleInputChange}
+ />
+
+
+ )
+}
+
+export default PublicHomeRoute
diff --git a/web/components/routes/anim.ts b/web/components/routes/public/anim.ts
similarity index 100%
rename from web/components/routes/anim.ts
rename to web/components/routes/public/anim.ts
diff --git a/web/components/routes/force-graph-client-lazy.tsx b/web/components/routes/public/force-graph-client-lazy.tsx
similarity index 100%
rename from web/components/routes/force-graph-client-lazy.tsx
rename to web/components/routes/public/force-graph-client-lazy.tsx
diff --git a/web/components/routes/graph-data.json b/web/components/routes/public/graph-data.json
similarity index 100%
rename from web/components/routes/graph-data.json
rename to web/components/routes/public/graph-data.json
diff --git a/web/package.json b/web/package.json
index db76c0bb..9ddd9b66 100644
--- a/web/package.json
+++ b/web/package.json
@@ -9,7 +9,7 @@
"test": "jest"
},
"dependencies": {
- "@clerk/nextjs": "^5.4.0",
+ "@clerk/nextjs": "^5.4.1",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@hookform/resolvers": "^3.9.0",
@@ -32,7 +32,7 @@
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
- "@tanstack/react-virtual": "^3.10.6",
+ "@tanstack/react-virtual": "^3.10.7",
"@tiptap/core": "^2.6.6",
"@tiptap/extension-blockquote": "^2.6.6",
"@tiptap/extension-bold": "^2.6.6",
@@ -67,7 +67,7 @@
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
- "framer-motion": "^11.4.0",
+ "framer-motion": "^11.5.2",
"jazz-react": "0.7.35-unique.2",
"jazz-react-auth-clerk": "0.7.33-new-auth.1",
"jazz-tools": "0.7.35-unique.2",
@@ -96,14 +96,14 @@
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@types/jest": "^29.5.12",
- "@types/node": "^22.5.2",
+ "@types/node": "^22.5.3",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"eslint": "^8.57.0",
"eslint-config-next": "14.2.5",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
- "postcss": "^8.4.44",
+ "postcss": "^8.4.45",
"prettier-plugin-tailwindcss": "^0.6.6",
"tailwindcss": "^3.4.10",
"ts-jest": "^29.2.5",