/
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 ({
/>
-
-
-
+ {session ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
{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}
/>
)
}