From e5d310790f4bf8aa59067645294e18e8174750f1 Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Sun, 5 Apr 2026 12:52:34 -0700 Subject: [PATCH 01/23] wip --- CLAUDE.md | 12 + packages/db/tools/scriptRunner.ts | 4 +- packages/db/tools/scripts/inject-chat-data.ts | 86 +++++ packages/web/package.json | 12 +- packages/web/src/app/(app)/browse/layout.tsx | 4 +- .../web/src/app/(app)/browse/layoutClient.tsx | 30 +- .../chat/[id]/components/chatThreadPanel.tsx | 39 +- packages/web/src/app/(app)/chat/[id]/page.tsx | 43 +-- .../chat/components/chatActionsDropdown.tsx | 2 +- packages/web/src/app/(app)/chat/page.tsx | 32 +- .../src/app/(app)/components/appSidebar.tsx | 319 ++++++++++++++++ .../web/src/app/(app)/components/topBar.tsx | 2 +- packages/web/src/app/(app)/layout.tsx | 21 +- packages/web/src/app/(app)/page.tsx | 1 - packages/web/src/app/(app)/repos/layout.tsx | 21 +- .../search/components/searchLandingPage.tsx | 5 +- .../search/components/searchResultsPage.tsx | 37 +- .../web/src/app/(app)/settings/layout.tsx | 6 +- packages/web/src/app/globals.css | 6 +- packages/web/src/components/ui/sidebar.tsx | 40 +- packages/web/src/components/ui/tooltip.tsx | 2 +- .../features/analytics/analyticsContent.tsx | 4 +- packages/web/src/hooks/use-mobile.tsx | 19 + packages/web/tailwind.config.ts | 1 + yarn.lock | 354 ++++++++++++++++-- 25 files changed, 887 insertions(+), 215 deletions(-) create mode 100644 packages/db/tools/scripts/inject-chat-data.ts create mode 100644 packages/web/src/app/(app)/components/appSidebar.tsx create mode 100644 packages/web/src/hooks/use-mobile.tsx diff --git a/CLAUDE.md b/CLAUDE.md index 943898eae..f7400eeaa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,6 +57,18 @@ if (!value) { return; } if (condition) doSomething(); ``` +## Conditional ClassNames + +Use `cn()` from `@/lib/utils` for conditional classNames instead of template literal interpolation: + +```tsx +// Correct +className={cn("border-b transition-colors", isActive ? "border-foreground" : "border-transparent")} + +// Incorrect +className={`border-b transition-colors ${isActive ? "border-foreground" : "border-transparent"}`} +``` + ## Tailwind CSS Use Tailwind color classes directly instead of CSS variable syntax: diff --git a/packages/db/tools/scriptRunner.ts b/packages/db/tools/scriptRunner.ts index 9732b9a84..599409053 100644 --- a/packages/db/tools/scriptRunner.ts +++ b/packages/db/tools/scriptRunner.ts @@ -7,6 +7,7 @@ import { injectUserData } from "./scripts/inject-user-data"; import { confirmAction } from "./utils"; import { injectRepoData } from "./scripts/inject-repo-data"; import { testRepoQueryPerf } from "./scripts/test-repo-query-perf"; +import { injectChatData } from "./scripts/inject-chat-data"; export interface Script { run: (prisma: PrismaClient) => Promise; @@ -19,12 +20,13 @@ export const scripts: Record = { "inject-user-data": injectUserData, "inject-repo-data": injectRepoData, "test-repo-query-perf": testRepoQueryPerf, + "inject-chat-data": injectChatData, } const parser = new ArgumentParser(); parser.add_argument("--url", { required: true, help: "Database URL" }); parser.add_argument("--script", { required: true, help: "Script to run" }); -const args = parser.parse_args(); +const [args] = parser.parse_known_args(); (async () => { if (!(args.script in scripts)) { diff --git a/packages/db/tools/scripts/inject-chat-data.ts b/packages/db/tools/scripts/inject-chat-data.ts new file mode 100644 index 000000000..89c0befa4 --- /dev/null +++ b/packages/db/tools/scripts/inject-chat-data.ts @@ -0,0 +1,86 @@ +import { Script } from "../scriptRunner"; +import { PrismaClient } from "../../dist"; +import { confirmAction } from "../utils"; + +const chatNames = [ + "How does the auth middleware work?", + "Explain the search indexing pipeline", + "Where are API routes defined?", + "How to add a new database migration", + "What is the repo sync process?", + "Understanding the chat architecture", + "How does SSO integration work?", + "Explain the permission model", + "Where is the webhook handler?", + "How to configure environment variables", + "Understanding the billing system", + "How does the worker process jobs?", + "Explain the caching strategy", + "Where are the shared types defined?", + "How does code search ranking work?", + "Understanding the notification system", + "How to add a new API endpoint", + "Explain the deployment pipeline", + "Where is error handling centralized?", + "How does real-time updates work?", + "Understanding the plugin system", + "How to write integration tests", + "Explain the file indexing process", + "Where are the email templates?", + "How does rate limiting work?", + "Understanding the monorepo structure", + "How to add a new feature flag", + "Explain the logging setup", + "Where is the GraphQL schema?", + "How does the sidebar component work?", +]; + +export const injectChatData: Script = { + run: async (prisma: PrismaClient) => { + const orgId = 1; + + const org = await prisma.org.findUnique({ + where: { id: orgId } + }); + + if (!org) { + console.error(`Organization with id ${orgId} not found.`); + return; + } + + const userIdArg = process.argv.find(arg => arg.startsWith("--user-id="))?.split("=")[1]; + + const user = userIdArg + ? await prisma.user.findUnique({ where: { id: userIdArg } }) + : await prisma.user.findFirst({ + where: { + orgs: { + some: { orgId } + } + } + }); + + if (!user) { + console.error(userIdArg + ? `User with id "${userIdArg}" not found.` + : `No user found in org ${orgId}.` + ); + return; + } + + await confirmAction(`This will create ${chatNames.length} chats for user "${user.name ?? user.email}" in org ${orgId}.`); + + for (const name of chatNames) { + await prisma.chat.create({ + data: { + name, + orgId, + createdById: user.id, + messages: [], + } + }); + } + + console.log(`Created ${chatNames.length} chats.`); + } +}; diff --git a/packages/web/package.json b/packages/web/package.json index d9533df10..393609057 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -70,7 +70,7 @@ "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.11", - "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-hover-card": "^1.1.6", "@radix-ui/react-icons": "^1.3.0", @@ -79,13 +79,13 @@ "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-select": "^2.1.6", - "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.4", "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-toggle": "^1.1.0", - "@radix-ui/react-tooltip": "^1.1.4", + "@radix-ui/react-tooltip": "^1.2.8", "@react-email/components": "^1.0.2", "@react-email/render": "^2.0.0", "@replit/codemirror-lang-csharp": "^6.2.0", @@ -114,7 +114,7 @@ "ai": "^6.0.105", "ajv": "^8.17.1", "bcryptjs": "^3.0.2", - "class-variance-authority": "^0.7.0", + "class-variance-authority": "^0.7.1", "client-only": "^0.0.1", "clsx": "^2.1.1", "cm6-graphql": "^0.2.0", @@ -149,7 +149,7 @@ "langfuse": "^3.38.4", "langfuse-vercel": "^3.38.4", "linguist-languages": "^9.3.1", - "lucide-react": "^0.517.0", + "lucide-react": "^1.7.0", "micromatch": "^4.0.8", "minidenticons": "^4.2.1", "next": "16.1.6", diff --git a/packages/web/src/app/(app)/browse/layout.tsx b/packages/web/src/app/(app)/browse/layout.tsx index 3ea59b9d5..d9b555c0a 100644 --- a/packages/web/src/app/(app)/browse/layout.tsx +++ b/packages/web/src/app/(app)/browse/layout.tsx @@ -1,4 +1,3 @@ -import { auth } from "@/auth"; import { LayoutClient } from "./layoutClient"; import { getConfiguredLanguageModelsInfo } from "@/features/chat/utils.server"; @@ -9,10 +8,9 @@ interface LayoutProps { export default async function Layout({ children, }: LayoutProps) { - const session = await auth(); const languageModels = await getConfiguredLanguageModelsInfo(); return ( - 0}> + 0}> {children} ) diff --git a/packages/web/src/app/(app)/browse/layoutClient.tsx b/packages/web/src/app/(app)/browse/layoutClient.tsx index b69dc4ad1..0d54e2cc7 100644 --- a/packages/web/src/app/(app)/browse/layoutClient.tsx +++ b/packages/web/src/app/(app)/browse/layoutClient.tsx @@ -5,40 +5,38 @@ import { BottomPanel } from "./components/bottomPanel"; import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle"; import { BrowseStateProvider } from "./browseStateProvider"; import { FileTreePanel } from "./components/fileTreePanel"; -import { TopBar } from "@/app/(app)/components/topBar"; import { useBrowseParams } from "./hooks/useBrowseParams"; import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog"; import { SearchBar } from "../components/searchBar"; import escapeStringRegexp from "escape-string-regexp"; -import { Session } from "next-auth"; +import { Separator } from "@/components/ui/separator"; interface LayoutProps { children: React.ReactNode; - session: Session | null; isSearchAssistSupported: boolean; } export function LayoutClient({ children, - session, isSearchAssistSupported, }: LayoutProps) { const { repoName, revisionName } = useBrowseParams(); return (
- - - +
+
+ +
+ +
diff --git a/packages/web/src/app/(app)/chat/[id]/components/chatThreadPanel.tsx b/packages/web/src/app/(app)/chat/[id]/components/chatThreadPanel.tsx index d801fc07d..85d15d634 100644 --- a/packages/web/src/app/(app)/chat/[id]/components/chatThreadPanel.tsx +++ b/packages/web/src/app/(app)/chat/[id]/components/chatThreadPanel.tsx @@ -1,6 +1,5 @@ 'use client'; -import { ResizablePanel } from '@/components/ui/resizable'; import { ChatThread } from '@/features/chat/components/chatThread'; import { LanguageModelInfo, SBChatMessage, SearchScope, SetChatStatePayload } from '@/features/chat/types'; import { SELECTED_SEARCH_SCOPES_LOCAL_STORAGE_KEY, SET_CHAT_STATE_SESSION_STORAGE_KEY } from '@/features/chat/constants'; @@ -14,7 +13,6 @@ interface ChatThreadPanelProps { languageModels: LanguageModelInfo[]; repos: RepositoryQuery[]; searchContexts: SearchContextQuery[]; - order: number; messages: SBChatMessage[]; isOwner: boolean; isAuthenticated: boolean; @@ -25,7 +23,6 @@ export const ChatThreadPanel = ({ languageModels, repos, searchContexts, - order, messages, isOwner, isAuthenticated, @@ -65,26 +62,20 @@ export const ChatThreadPanel = ({ }, [chatState, setChatState]); return ( - -
- -
-
+
+ +
) } \ No newline at end of file diff --git a/packages/web/src/app/(app)/chat/[id]/page.tsx b/packages/web/src/app/(app)/chat/[id]/page.tsx index 9add839cc..45c565e6b 100644 --- a/packages/web/src/app/(app)/chat/[id]/page.tsx +++ b/packages/web/src/app/(app)/chat/[id]/page.tsx @@ -1,5 +1,5 @@ import { getRepos, getSearchContexts } from '@/actions'; -import { getUserChatHistory, getChatInfo, claimAnonymousChats, getSharedWithUsersForChat } from '@/features/chat/actions'; +import { getChatInfo, claimAnonymousChats, getSharedWithUsersForChat } from '@/features/chat/actions'; import { getConfiguredLanguageModelsInfo } from "@/features/chat/utils.server"; import { ServiceErrorException } from '@/lib/serviceError'; import { isServiceError } from '@/lib/utils'; @@ -10,9 +10,6 @@ import { TopBar } from '../../components/topBar'; import { ChatName } from '../components/chatName'; import { ShareChatPopover } from '../components/shareChatPopover'; import { auth } from '@/auth'; -import { AnimatedResizableHandle } from '@/components/ui/animatedResizableHandle'; -import { ChatSidePanel } from '../components/chatSidePanel'; -import { ResizablePanelGroup } from '@/components/ui/resizable'; import { __unsafePrisma } from '@/prisma'; import { ChatVisibility } from '@sourcebot/db'; import { Metadata } from 'next'; @@ -96,12 +93,6 @@ export default async function Page(props: PageProps) { const repos = await getRepos(); const searchContexts = await getSearchContexts(); const chatInfo = await getChatInfo({ chatId: params.id }); - const chatHistory = session ? await getUserChatHistory() : []; - - if (isServiceError(chatHistory)) { - throw new ServiceErrorException(chatHistory); - } - if (isServiceError(repos)) { throw new ServiceErrorException(repos); } @@ -142,7 +133,7 @@ export default async function Page(props: PageProps) { const hasChatSharingEntitlement = hasEntitlement('chat-sharing'); return ( -
+
) : undefined} /> - - - - - +
) } diff --git a/packages/web/src/app/(app)/chat/components/chatActionsDropdown.tsx b/packages/web/src/app/(app)/chat/components/chatActionsDropdown.tsx index 8aba6b53a..8c84a971b 100644 --- a/packages/web/src/app/(app)/chat/components/chatActionsDropdown.tsx +++ b/packages/web/src/app/(app)/chat/components/chatActionsDropdown.tsx @@ -21,7 +21,7 @@ export const ChatActionsDropdown = ({ align = "start", }: ChatActionsDropdownProps) => { return ( - + {children} diff --git a/packages/web/src/app/(app)/chat/page.tsx b/packages/web/src/app/(app)/chat/page.tsx index a072bc2b7..1c1603371 100644 --- a/packages/web/src/app/(app)/chat/page.tsx +++ b/packages/web/src/app/(app)/chat/page.tsx @@ -1,29 +1,23 @@ import { getRepos, getReposStats, getSearchContexts } from "@/actions"; import { SourcebotLogo } from "@/app/components/sourcebotLogo"; -import { getUserChatHistory } from "@/features/chat/actions"; import { getConfiguredLanguageModelsInfo } from "@/features/chat/utils.server"; import { CustomSlateEditor } from "@/features/chat/customSlateEditor"; import { ServiceErrorException } from "@/lib/serviceError"; import { isServiceError, measure } from "@/lib/utils"; import { LandingPageChatBox } from "./components/landingPageChatBox"; import { RepositoryCarousel } from "../components/repositoryCarousel"; -import { NavigationMenu } from "../components/navigationMenu"; import { Separator } from "@/components/ui/separator"; import { DemoCards } from "./components/demoCards"; import { env } from "@sourcebot/shared"; import { loadJsonFile } from "@sourcebot/shared"; import { DemoExamples, demoExamplesSchema } from "@/types"; import { auth } from "@/auth"; -import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; -import { ChatSidePanel } from "./components/chatSidePanel"; -import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle"; export default async function Page() { const languageModels = await getConfiguredLanguageModelsInfo(); const searchContexts = await getSearchContexts(); const allRepos = await getRepos(); const session = await auth(); - const chatHistory = session ? await getUserChatHistory() : []; const carouselRepos = await getRepos({ where: { @@ -52,10 +46,6 @@ export default async function Page() { throw new ServiceErrorException(repoStats); } - if (isServiceError(chatHistory)) { - throw new ServiceErrorException(chatHistory); - } - const demoExamples = env.SOURCEBOT_DEMO_EXAMPLES_PATH ? await (async () => { try { return (await measure(() => loadJsonFile(env.SOURCEBOT_DEMO_EXAMPLES_PATH!, demoExamplesSchema), 'loadExamplesJsonFile')).data; @@ -66,25 +56,7 @@ export default async function Page() { })() : undefined; return ( -
- - - - - +
)}
- -
) } \ No newline at end of file diff --git a/packages/web/src/app/(app)/components/appSidebar.tsx b/packages/web/src/app/(app)/components/appSidebar.tsx new file mode 100644 index 000000000..8d69197fa --- /dev/null +++ b/packages/web/src/app/(app)/components/appSidebar.tsx @@ -0,0 +1,319 @@ +"use client"; + +import { ChatActionsDropdown } from "@/app/(app)/chat/components/chatActionsDropdown"; +import { DeleteChatDialog } from "@/app/(app)/chat/components/deleteChatDialog"; +import { DuplicateChatDialog } from "@/app/(app)/chat/components/duplicateChatDialog"; +import { RenameChatDialog } from "@/app/(app)/chat/components/renameChatDialog"; +import { SourcebotLogo } from "@/app/components/sourcebotLogo"; +import { useToast } from "@/components/hooks/use-toast"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarHeader, + SidebarMenu, + SidebarMenuAction, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar"; +import { UserAvatar } from "@/components/userAvatar"; +import { deleteChat, duplicateChat, updateChatName } from "@/features/chat/actions"; +import { captureEvent } from "@/hooks/useCaptureEvent"; +import { cn, isServiceError } from "@/lib/utils"; +import { BookMarkedIcon, ChevronsUpDown, EllipsisIcon, LogOut, MessageCircleIcon, MessagesSquareIcon, SearchIcon, SettingsIcon } from "lucide-react"; +import { Session } from "next-auth"; +import { signOut } from "next-auth/react"; +import Link from "next/link"; +import { usePathname, useRouter } from "next/navigation"; +import posthog from "posthog-js"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { AppearanceDropdownMenuGroup } from "./appearanceDropdownMenuGroup"; + + +const items = [ + { title: "Ask", href: "/chat", icon: MessageCircleIcon }, + { title: "Code Search", href: "/search", icon: SearchIcon }, + { title: "Chats", href: "/chats", icon: MessagesSquareIcon }, + { title: "Repositories", href: "/repos", icon: BookMarkedIcon }, + { title: "Settings", href: "/settings", icon: SettingsIcon }, +]; + +export interface ChatHistoryItem { + id: string; + name: string | null; + createdAt: Date; +} + +interface AppSidebarProps { + session: Session | null; + chatHistory: ChatHistoryItem[]; +} + +export function AppSidebar({ session, chatHistory }: AppSidebarProps) { + const pathname = usePathname(); + const router = useRouter(); + const { toast } = useToast(); + + const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false); + const [chatIdToRename, setChatIdToRename] = useState(null); + const [isDuplicateDialogOpen, setIsDuplicateDialogOpen] = useState(false); + const [chatIdToDuplicate, setChatIdToDuplicate] = useState(null); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [chatIdToDelete, setChatIdToDelete] = useState(null); + const [isScrolled, setIsScrolled] = useState(false); + const contentRef = useRef(null); + + useEffect(() => { + const el = contentRef.current; + if (!el) { + return; + } + const handleScroll = () => setIsScrolled(el.scrollTop > 0); + el.addEventListener("scroll", handleScroll); + return () => el.removeEventListener("scroll", handleScroll); + }, []); + + const isActive = (href: string) => { + if (href === "/search") { + return pathname === "/" || pathname.startsWith("/search"); + } + if (href === "/chat") { + return pathname === "/chat"; + } + return pathname.startsWith(href); + }; + + const onRenameChat = useCallback(async (name: string, chatId: string): Promise => { + const response = await updateChatName({ chatId, name }); + if (isServiceError(response)) { + toast({ description: `Failed to rename chat. Reason: ${response.message}` }); + return false; + } + toast({ description: "Chat renamed successfully" }); + captureEvent('wa_chat_renamed', { chatId }); + router.refresh(); + return true; + }, [router, toast]); + + const onDeleteChat = useCallback(async (chatIdToDelete: string): Promise => { + const response = await deleteChat({ chatId: chatIdToDelete }); + if (isServiceError(response)) { + toast({ description: `Failed to delete chat. Reason: ${response.message}` }); + return false; + } + toast({ description: "Chat deleted successfully" }); + captureEvent('wa_chat_deleted', { chatId: chatIdToDelete }); + if (pathname === `/chat/${chatIdToDelete}`) { + router.push("/chat"); + } + return true; + }, [pathname, router, toast]); + + const onDuplicateChat = useCallback(async (newName: string, chatIdToDuplicate: string): Promise => { + const response = await duplicateChat({ chatId: chatIdToDuplicate, newName }); + if (isServiceError(response)) { + toast({ description: `Failed to duplicate chat. Reason: ${response.message}` }); + return null; + } + toast({ description: "Chat duplicated successfully" }); + captureEvent('wa_chat_duplicated', { chatId: chatIdToDuplicate }); + router.push(`/chat/${response.id}`); + return response.id; + }, [router, toast]); + + return ( + + + +
+ +
+
+ +
+ + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + +
+ + {chatHistory.length > 0 && ( + + Recent Chats + + + {chatHistory.map((chat) => ( + + + + {chat.name ?? "Untitled chat"} + + + { + setChatIdToRename(chat.id); + setIsRenameDialogOpen(true); + }} + onDuplicateClick={() => { + setChatIdToDuplicate(chat.id); + setIsDuplicateDialogOpen(true); + }} + onDeleteClick={() => { + setChatIdToDelete(chat.id); + setIsDeleteDialogOpen(true); + }} + > + + + + + + ))} + + + + )} + { + if (chatIdToRename) { + return await onRenameChat(name, chatIdToRename); + } + return false; + }} + currentName={chatHistory.find((chat) => chat.id === chatIdToRename)?.name ?? "Untitled chat"} + /> + { + if (chatIdToDelete) { + return await onDeleteChat(chatIdToDelete); + } + return false; + }} + /> + { + if (chatIdToDuplicate) { + return await onDuplicateChat(newName, chatIdToDuplicate); + } + return null; + }} + currentName={chatHistory.find((chat) => chat.id === chatIdToDuplicate)?.name ?? "Untitled chat"} + /> + + + {session && ( + + )} + +
+ ); +} + + +interface MeControlDropdownMenuProps { + session: Session; +} + +export const MeControlDropdownMenu = ({ + session, +}: MeControlDropdownMenuProps) => { + return ( + + + + + + +
+ {session.user.name ?? "User"} + {session.user.email && ( + {session.user.email} + )} +
+ +
+
+ + +
+ +
+

{session.user.name ?? "User"}

+ {session.user.email && ( +

{session.user.email}

+ )} +
+
+
+ + + + + + Settings + + + + + { + signOut({ + redirectTo: "/login", + }).then(() => { + posthog.reset(); + }) + }} + > + + Log out + + +
+
+
+
+ ) +} diff --git a/packages/web/src/app/(app)/components/topBar.tsx b/packages/web/src/app/(app)/components/topBar.tsx index ed5996c6c..a93937b07 100644 --- a/packages/web/src/app/(app)/components/topBar.tsx +++ b/packages/web/src/app/(app)/components/topBar.tsx @@ -33,7 +33,7 @@ export const TopBar = ({ return (
-
+
{ @@ -175,7 +183,18 @@ export default async function Layout(props: LayoutProps) { /> ) : null } - {children} +
+ + +
+
+
+ {children} +
+
+
+
+
{env.EXPERIMENT_ASK_GH_ENABLED !== 'true' && } diff --git a/packages/web/src/app/(app)/page.tsx b/packages/web/src/app/(app)/page.tsx index 142220f55..7ab18c79a 100644 --- a/packages/web/src/app/(app)/page.tsx +++ b/packages/web/src/app/(app)/page.tsx @@ -5,6 +5,5 @@ interface Props { } export default async function Home(props: Props) { - // Default to rendering the search page. return ; } \ No newline at end of file diff --git a/packages/web/src/app/(app)/repos/layout.tsx b/packages/web/src/app/(app)/repos/layout.tsx index 0159df9c5..88738d22e 100644 --- a/packages/web/src/app/(app)/repos/layout.tsx +++ b/packages/web/src/app/(app)/repos/layout.tsx @@ -1,10 +1,6 @@ -import { InfoIcon } from "lucide-react"; -import { NavigationMenu } from "../components/navigationMenu"; -import Link from "next/link"; -import { getCurrentUserRole, getReposStats } from "@/actions"; -import { isServiceError } from "@/lib/utils"; +import { getReposStats } from "@/actions"; import { ServiceErrorException } from "@/lib/serviceError"; -import { OrgRole } from "@sourcebot/db"; +import { isServiceError } from "@/lib/utils"; interface LayoutProps { children: React.ReactNode; @@ -20,18 +16,9 @@ export default async function Layout( throw new ServiceErrorException(repoStats); } - const userRoleInOrg = await getCurrentUserRole(); - return ( -
- - {(repoStats.numberOfRepos === 0 && userRoleInOrg === OrgRole.OWNER) && ( -
- - No repositories configured. Create a connection to get started. -
- )} -
+
+
{children} diff --git a/packages/web/src/app/(app)/search/components/searchLandingPage.tsx b/packages/web/src/app/(app)/search/components/searchLandingPage.tsx index 668c84e57..e9236607e 100644 --- a/packages/web/src/app/(app)/search/components/searchLandingPage.tsx +++ b/packages/web/src/app/(app)/search/components/searchLandingPage.tsx @@ -1,5 +1,4 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo" -import { NavigationMenu } from "../../components/navigationMenu" import { RepositoryCarousel } from "../../components/repositoryCarousel" import { Separator } from "@/components/ui/separator" import { SyntaxReferenceGuideHint } from "../../components/syntaxReferenceGuideHint" @@ -32,9 +31,7 @@ export const SearchLandingPage = async ({ if (isServiceError(repoStats)) throw new ServiceErrorException(repoStats); return ( -
- - +
- {/* TopBar */} - - - +
+
+ +
+ +
{error ? (
diff --git a/packages/web/src/app/(app)/settings/layout.tsx b/packages/web/src/app/(app)/settings/layout.tsx index 417042b1d..db880dd3f 100644 --- a/packages/web/src/app/(app)/settings/layout.tsx +++ b/packages/web/src/app/(app)/settings/layout.tsx @@ -1,7 +1,6 @@ import React from "react" import { Metadata } from "next" import { SidebarNav } from "./components/sidebar-nav" -import { NavigationMenu } from "../components/navigationMenu" import { redirect } from "next/navigation"; import { auth } from "@/auth"; import { isServiceError } from "@/lib/utils"; @@ -37,9 +36,8 @@ export default async function SettingsLayout( } return ( -
- -
+
+
diff --git a/packages/web/src/app/globals.css b/packages/web/src/app/globals.css index ca00492d2..f77b7293e 100644 --- a/packages/web/src/app/globals.css +++ b/packages/web/src/app/globals.css @@ -53,6 +53,7 @@ --sidebar-border: hsl(220 13% 91%); --sidebar-ring: hsl(217.2 91.2% 59.8%); --link: hsl(217, 91%, 60%); + --shell: hsl(253, 23%, 92%); --editor-font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; --editor-font-size: 13px; @@ -113,7 +114,7 @@ } .dark { - --background: hsl(222.2 84% 4.9%); + --background: hsl(0, 0%, 6%); --background-secondary: hsl(222.2 84% 4.9%); --foreground: hsl(210 40% 98%); --card: hsl(222.2 84% 4.9%); @@ -140,7 +141,7 @@ --chart-4: hsl(280 65% 60%); --chart-5: hsl(340 75% 55%); --highlight: hsl(217 91% 60%); - --sidebar-background: hsl(240 5.9% 10%); + --sidebar-background: hsl(0, 0%, 3%); --sidebar-foreground: hsl(240 4.8% 95.9%); --sidebar-primary: hsl(224.3 76.3% 48%); --sidebar-primary-foreground: hsl(0 0% 100%); @@ -149,6 +150,7 @@ --sidebar-border: hsl(240 3.7% 15.9%); --sidebar-ring: hsl(217.2 91.2% 59.8%); --link: hsl(217, 91%, 60%); + --shell: hsl(0, 0%, 3%); --editor-background: var(--background); --editor-foreground: #abb2bf; diff --git a/packages/web/src/components/ui/sidebar.tsx b/packages/web/src/components/ui/sidebar.tsx index 9e5d91636..5158ed504 100644 --- a/packages/web/src/components/ui/sidebar.tsx +++ b/packages/web/src/components/ui/sidebar.tsx @@ -2,15 +2,21 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" -import { VariantProps, cva } from "class-variance-authority" +import { cva, type VariantProps } from "class-variance-authority" import { PanelLeft } from "lucide-react" -import { useIsMobile } from "@/components/hooks/use-mobile" +import { useIsMobile } from "@/hooks/use-mobile" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" -import { Sheet, SheetContent } from "@/components/ui/sheet" +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet" import { Skeleton } from "@/components/ui/skeleton" import { Tooltip, @@ -26,7 +32,7 @@ const SIDEBAR_WIDTH_MOBILE = "18rem" const SIDEBAR_WIDTH_ICON = "3rem" const SIDEBAR_KEYBOARD_SHORTCUT = "b" -type SidebarContext = { +type SidebarContextProps = { state: "expanded" | "collapsed" open: boolean setOpen: (open: boolean) => void @@ -36,7 +42,7 @@ type SidebarContext = { toggleSidebar: () => void } -const SidebarContext = React.createContext(null) +const SidebarContext = React.createContext(null) function useSidebar() { const context = React.useContext(SidebarContext) @@ -116,7 +122,7 @@ const SidebarProvider = React.forwardRef< // This makes it easier to style the sidebar with Tailwind classes. const state = open ? "expanded" : "collapsed" - const contextValue = React.useMemo( + const contextValue = React.useMemo( () => ({ state, open, @@ -206,6 +212,10 @@ const Sidebar = React.forwardRef< } side={side} > + + Sidebar + Displays the mobile sidebar. +
{children}
@@ -215,7 +225,7 @@ const Sidebar = React.forwardRef< return (