diff --git a/CHANGELOG.md b/CHANGELOG.md index 49b64aa39..e28ac8b48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Changed the me-control to render the user's avatar in the top-bar. [#874](https://github.com/sourcebot-dev/sourcebot/pull/874) +- Moved the "current version" indicator into the "what's new" dropdown. [#874](https://github.com/sourcebot-dev/sourcebot/pull/874) + +### Removed +- Removed the Discord and GitHub buttons from the top corner. [#874](https://github.com/sourcebot-dev/sourcebot/pull/874) + ## [4.10.29] - 2026-02-10 ### Changed diff --git a/packages/web/src/app/[domain]/browse/layout.tsx b/packages/web/src/app/[domain]/browse/layout.tsx index ce1d66a97..47b07118d 100644 --- a/packages/web/src/app/[domain]/browse/layout.tsx +++ b/packages/web/src/app/[domain]/browse/layout.tsx @@ -1,72 +1,17 @@ -'use client'; - -import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; -import { BottomPanel } from "./components/bottomPanel"; -import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle"; -import { BrowseStateProvider } from "./browseStateProvider"; -import { FileTreePanel } from "./components/fileTreePanel"; -import { TopBar } from "@/app/[domain]/components/topBar"; -import { useBrowseParams } from "./hooks/useBrowseParams"; -import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog"; -import { useDomain } from "@/hooks/useDomain"; -import { SearchBar } from "../components/searchBar"; -import escapeStringRegexp from "escape-string-regexp"; +import { auth } from "@/auth"; +import { LayoutClient } from "./layoutClient"; interface LayoutProps { children: React.ReactNode; } -export default function Layout({ +export default async function Layout({ children, }: LayoutProps) { - const { repoName, revisionName } = useBrowseParams(); - const domain = useDomain(); - + const session = await auth(); return ( - -
- - - - - - - - - - - - {children} - - - - - - -
- -
- ); + + {children} + + ) } diff --git a/packages/web/src/app/[domain]/browse/layoutClient.tsx b/packages/web/src/app/[domain]/browse/layoutClient.tsx new file mode 100644 index 000000000..87f7b4048 --- /dev/null +++ b/packages/web/src/app/[domain]/browse/layoutClient.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; +import { BottomPanel } from "./components/bottomPanel"; +import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle"; +import { BrowseStateProvider } from "./browseStateProvider"; +import { FileTreePanel } from "./components/fileTreePanel"; +import { TopBar } from "@/app/[domain]/components/topBar"; +import { useBrowseParams } from "./hooks/useBrowseParams"; +import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog"; +import { useDomain } from "@/hooks/useDomain"; +import { SearchBar } from "../components/searchBar"; +import escapeStringRegexp from "escape-string-regexp"; +import { Session } from "next-auth"; + +interface LayoutProps { + children: React.ReactNode; + session: Session | null; +} + +export function LayoutClient({ + children, + session, +}: LayoutProps) { + const { repoName, revisionName } = useBrowseParams(); + const domain = useDomain(); + + return ( + +
+ + + + + + + + + + + + {children} + + + + + + +
+ +
+ ); +} diff --git a/packages/web/src/app/[domain]/chat/[id]/page.tsx b/packages/web/src/app/[domain]/chat/[id]/page.tsx index c22cbd0c9..1f1d0aa6f 100644 --- a/packages/web/src/app/[domain]/chat/[id]/page.tsx +++ b/packages/web/src/app/[domain]/chat/[id]/page.tsx @@ -57,6 +57,7 @@ export default async function Page(props: PageProps) {
/ diff --git a/packages/web/src/app/[domain]/components/appearanceDropdownMenu.tsx b/packages/web/src/app/[domain]/components/appearanceDropdownMenu.tsx new file mode 100644 index 000000000..1ca49a946 --- /dev/null +++ b/packages/web/src/app/[domain]/components/appearanceDropdownMenu.tsx @@ -0,0 +1,33 @@ +import { Button } from "@/components/ui/button" +import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Settings2Icon } from "lucide-react" +import { AppearanceDropdownMenuGroup } from "./appearanceDropdownMenuGroup" +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" + +interface AppearanceDropdownMenuProps { + className?: string; +} + +export const AppearanceDropdownMenu = ({ className }: AppearanceDropdownMenuProps) => { + return ( + + + + + + + + + Appearance settings + + + + + + + ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/appearanceDropdownMenuGroup.tsx b/packages/web/src/app/[domain]/components/appearanceDropdownMenuGroup.tsx new file mode 100644 index 000000000..d3526684d --- /dev/null +++ b/packages/web/src/app/[domain]/components/appearanceDropdownMenuGroup.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger +} from "@/components/ui/dropdown-menu" +import { useKeymapType } from "@/hooks/useKeymapType" +import { KeymapType } from "@/lib/types" +import { + CodeIcon, + Laptop, + Moon, + Sun +} from "lucide-react" +import { useTheme } from "next-themes" +import { useMemo } from "react" + +export const AppearanceDropdownMenuGroup = () => { + const { theme: _theme, setTheme } = useTheme(); + const [keymapType, setKeymapType] = useKeymapType(); + + const theme = useMemo(() => { + return _theme ?? "light"; + }, [_theme]); + + const ThemeIcon = useMemo(() => { + switch (theme) { + case "light": + return ; + case "dark": + return ; + case "system": + return ; + default: + return ; + } + }, [theme]); + + return ( + + + + {ThemeIcon} + Theme + + + + + + Light + + + Dark + + + System + + + + + + + + + Code navigation + + + + setKeymapType(value as KeymapType)}> + + Default + + + Vim + + + + + + + ) +} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/meControlDropdownMenu.tsx b/packages/web/src/app/[domain]/components/meControlDropdownMenu.tsx new file mode 100644 index 000000000..2f7366dd8 --- /dev/null +++ b/packages/web/src/app/[domain]/components/meControlDropdownMenu.tsx @@ -0,0 +1,90 @@ +'use client'; + +import { + LogOut, + Settings, +} from "lucide-react" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { cn } from "@/lib/utils" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { signOut } from "next-auth/react" +import posthog from "posthog-js"; +import { useDomain } from "@/hooks/useDomain"; +import { Session } from "next-auth"; +import { AppearanceDropdownMenuGroup } from "./appearanceDropdownMenuGroup"; +import placeholderAvatar from "@/public/placeholder_avatar.png"; + +interface MeControlDropdownMenuProps { + menuButtonClassName?: string; + session: Session; +} + +export const MeControlDropdownMenu = ({ + menuButtonClassName, + session, +}: MeControlDropdownMenuProps) => { + const domain = useDomain(); + + return ( + + + + + + {session.user.name && session.user.name.length > 0 ? session.user.name[0].toUpperCase() : 'U'} + + + + + +
+ + + + {session.user.name && session.user.name.length > 0 ? session.user.name[0].toUpperCase() : 'U'} + + +
+

{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/[domain]/components/navigationMenu/index.tsx b/packages/web/src/app/[domain]/components/navigationMenu/index.tsx index 0b089886f..8c70f5699 100644 --- a/packages/web/src/app/[domain]/components/navigationMenu/index.tsx +++ b/packages/web/src/app/[domain]/components/navigationMenu/index.tsx @@ -9,19 +9,17 @@ import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; import { env } from "@sourcebot/shared"; import { ServiceErrorException } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; -import { DiscordLogoIcon, GitHubLogoIcon } from "@radix-ui/react-icons"; import { OrgRole, RepoIndexingJobStatus, RepoIndexingJobType } from "@sourcebot/db"; import Link from "next/link"; -import { redirect } from "next/navigation"; import { OrgSelector } from "../orgSelector"; -import { SettingsDropdown } from "../settingsDropdown"; +import { MeControlDropdownMenu } from "../meControlDropdownMenu"; import WhatsNewIndicator from "../whatsNewIndicator"; import { NavigationItems } from "./navigationItems"; import { ProgressIndicator } from "./progressIndicator"; import { TrialIndicator } from "./trialIndicator"; +import { redirect } from "next/navigation"; +import { AppearanceDropdownMenu } from "../appearanceDropdownMenu"; -const SOURCEBOT_DISCORD_URL = "https://discord.gg/HDScTs3ptP"; -const SOURCEBOT_GITHUB_URL = "https://github.com/sourcebot-dev/sourcebot"; interface NavigationMenuProps { domain: string; @@ -138,35 +136,28 @@ export const NavigationMenu = async ({ /> -
{ - "use server"; - redirect(SOURCEBOT_DISCORD_URL); - }} - > - -
-
{ - "use server"; - redirect(SOURCEBOT_GITHUB_URL); - }} - > - -
- + {session ? ( + + ) : ( + <> +
{ + "use server"; + redirect("/login"); + }} + > + +
+ + + )}
diff --git a/packages/web/src/app/[domain]/components/settingsDropdown.tsx b/packages/web/src/app/[domain]/components/settingsDropdown.tsx deleted file mode 100644 index 0dc7b8ff8..000000000 --- a/packages/web/src/app/[domain]/components/settingsDropdown.tsx +++ /dev/null @@ -1,184 +0,0 @@ -'use client'; - -import { - CodeIcon, - Laptop, - LogIn, - LogOut, - Moon, - Settings, - Sun -} from "lucide-react" -import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuPortal, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuSeparator, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { useTheme } from "next-themes" -import { useMemo } from "react" -import { KeymapType } from "@/lib/types" -import { cn } from "@/lib/utils" -import { useKeymapType } from "@/hooks/useKeymapType" -import { useSession } from "next-auth/react"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { signOut } from "next-auth/react" -import { SOURCEBOT_VERSION, env } from "@sourcebot/shared/client"; -import posthog from "posthog-js"; -import { useDomain } from "@/hooks/useDomain"; -import Link from "next/link"; - -interface SettingsDropdownProps { - menuButtonClassName?: string; -} - -export const SettingsDropdown = ({ - menuButtonClassName, -}: SettingsDropdownProps) => { - - const { theme: _theme, setTheme } = useTheme(); - const [keymapType, setKeymapType] = useKeymapType(); - const { data: session } = useSession(); - const domain = useDomain(); - - const theme = useMemo(() => { - return _theme ?? "light"; - }, [_theme]); - - const ThemeIcon = useMemo(() => { - switch (theme) { - case "light": - return ; - case "dark": - return ; - case "system": - return ; - default: - return ; - } - }, [theme]); - - return ( - - - - - - {session?.user ? ( - -
- - - - {session.user.name && session.user.name.length > 0 ? session.user.name[0].toUpperCase() : 'U'} - - -
-

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

- {session.user.email && ( -

{session.user.email}

- )} -
-
- { - signOut({ - redirectTo: "/login", - }).then(() => { - posthog.reset(); - }) - }} - > - - Log out - -
- ) : ( - { - window.location.href = "/login"; - }} - > - - Sign in - - )} - - - - - {ThemeIcon} - Theme - - - - - - Light - - - Dark - - - System - - - - - - - - - Code navigation - - - - setKeymapType(value as KeymapType)}> - - Default - - - Vim - - - - - - {session?.user && ( - - - - Settings - - - )} - - -
- Version: {SOURCEBOT_VERSION} - {env.NEXT_PUBLIC_BUILD_COMMIT_SHA && ( - - ({env.NEXT_PUBLIC_BUILD_COMMIT_SHA.substring(0, 7)}) - - )} -
-
-
- ) -} diff --git a/packages/web/src/app/[domain]/components/topBar.tsx b/packages/web/src/app/[domain]/components/topBar.tsx index d9fadb89c..8424bad3b 100644 --- a/packages/web/src/app/[domain]/components/topBar.tsx +++ b/packages/web/src/app/[domain]/components/topBar.tsx @@ -1,21 +1,33 @@ +'use client'; + import Link from "next/link"; import Image from "next/image"; import logoLight from "@/public/sb_logo_light.png"; import logoDark from "@/public/sb_logo_dark.png"; -import { SettingsDropdown } from "./settingsDropdown"; +import { MeControlDropdownMenu } from "./meControlDropdownMenu"; import { Separator } from "@/components/ui/separator"; +import { Session } from "next-auth"; +import { Button } from "@/components/ui/button"; +import { LogIn } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { AppearanceDropdownMenu } from "./appearanceDropdownMenu"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; interface TopBarProps { domain: string; children?: React.ReactNode; homePath?: string; + session: Session | null; } export const TopBar = ({ domain, children, homePath = `/${domain}`, + session, }: TopBarProps) => { + const router = useRouter(); + return (
@@ -37,9 +49,35 @@ export const TopBar = ({ {children}
- + {session ? ( + + ) : ( +
+ + + + + + Sign in + + + +
+ )}
diff --git a/packages/web/src/app/[domain]/components/whatsNewIndicator.tsx b/packages/web/src/app/[domain]/components/whatsNewIndicator.tsx index 8259c03dc..296c2a706 100644 --- a/packages/web/src/app/[domain]/components/whatsNewIndicator.tsx +++ b/packages/web/src/app/[domain]/components/whatsNewIndicator.tsx @@ -9,6 +9,9 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover import { Badge } from "@/components/ui/badge" import { NewsItem } from "@/lib/types" import { newsData } from "@/lib/newsData" +import { env, SOURCEBOT_VERSION } from "@sourcebot/shared/client" +import Link from "next/link" +import { Separator } from "@/components/ui/separator" interface WhatsNewProps { newsItems?: NewsItem[] @@ -19,10 +22,10 @@ const COOKIE_NAME = "whats-new-read-items" const getReadItems = (): string[] => { if (typeof document === "undefined") return [] - + const cookies = document.cookie.split(';').map(cookie => cookie.trim()) const targetCookie = cookies.find(cookie => cookie.startsWith(`${COOKIE_NAME}=`)) - + if (!targetCookie) return [] try { @@ -36,12 +39,12 @@ const getReadItems = (): string[] => { const setReadItems = (readItems: string[]) => { if (typeof document === "undefined") return - + try { const expires = new Date() expires.setFullYear(expires.getFullYear() + 1) const cookieValue = encodeURIComponent(JSON.stringify(readItems)) - + document.cookie = `${COOKIE_NAME}=${cookieValue}; expires=${expires.toUTCString()}; path=/; SameSite=Lax` } catch (error) { console.warn('Failed to set whats-new cookie:', error) @@ -130,6 +133,7 @@ export default function WhatsNewIndicator({ newsItems = newsData, autoMarkAsRead )} +
{newsItemsWithReadState.length === 0 ? ( @@ -139,9 +143,8 @@ export default function WhatsNewIndicator({ newsItems = newsData, autoMarkAsRead {newsItemsWithReadState.map((item, index) => (
{!item.read &&
}
+ +
+ Current version: {SOURCEBOT_VERSION} + {env.NEXT_PUBLIC_BUILD_COMMIT_SHA && ( + + ({env.NEXT_PUBLIC_BUILD_COMMIT_SHA.substring(0, 7)}) + + )} +
) diff --git a/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx b/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx index ebd9c3cd9..bd2694500 100644 --- a/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx +++ b/packages/web/src/app/[domain]/search/components/searchResultsPage.tsx @@ -34,12 +34,14 @@ import { FilterPanel } from "./filterPanel"; import { useFilteredMatches } from "./filterPanel/useFilterMatches"; import { SearchResultsPanel, SearchResultsPanelHandle } from "./searchResultsPanel"; import { ServiceErrorException } from "@/lib/serviceError"; +import { Session } from "next-auth"; interface SearchResultsPageProps { searchQuery: string; defaultMaxMatchCount: number; isRegexEnabled: boolean; isCaseSensitivityEnabled: boolean; + session: Session | null; } export const SearchResultsPage = ({ @@ -47,6 +49,7 @@ export const SearchResultsPage = ({ defaultMaxMatchCount, isRegexEnabled, isCaseSensitivityEnabled, + session, }: SearchResultsPageProps) => { const router = useRouter(); const { setSearchHistory } = useSearchHistory(); @@ -170,6 +173,7 @@ export const SearchResultsPage = ({ {/* TopBar */} ; @@ -18,6 +19,8 @@ export default async function SearchPage(props: SearchPageProps) { const isRegexEnabled = searchParams?.isRegexEnabled === "true"; const isCaseSensitivityEnabled = searchParams?.isCaseSensitivityEnabled === "true"; + const session = await auth(); + if (query === undefined || query.length === 0) { return } @@ -28,6 +31,7 @@ export default async function SearchPage(props: SearchPageProps) { defaultMaxMatchCount={env.DEFAULT_MAX_MATCH_COUNT} isRegexEnabled={isRegexEnabled} isCaseSensitivityEnabled={isCaseSensitivityEnabled} + session={session} /> ) }