Move logout button & profile picture into settings dropdown (#172)

This commit is contained in:
Brendan Kellam 2025-01-19 12:52:13 -08:00 committed by GitHub
parent 5d253ffa12
commit a5091fb900
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 65 additions and 67 deletions

View file

@ -5,8 +5,6 @@ import { Separator } from "@/components/ui/separator";
import Image from "next/image";
import logoDark from "../../../public/sb_logo_dark_small.png";
import logoLight from "../../../public/sb_logo_light_small.png";
import { ProfilePicture } from "./profilePicture";
import { signOut } from "@/auth";
import { SettingsDropdown } from "./settingsDropdown";
import { GitHubLogoIcon, DiscordLogoIcon } from "@radix-ui/react-icons";
import { redirect } from "next/navigation";
@ -88,21 +86,6 @@ export const NavigationMenu = async () => {
</Button>
</form>
<SettingsDropdown />
<form
action={async () => {
"use server";
await signOut();
}}
>
<Button
type="submit"
variant="outline"
size="default"
>
Logout
</Button>
</form>
<ProfilePicture />
</div>
</div>
<Separator />

View file

@ -1,20 +0,0 @@
import { auth } from "@/auth"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar"
export const ProfilePicture = async () => {
const session = await auth()
return (
<Avatar>
<AvatarImage
src={session?.user?.image ?? ""}
alt="@shadcn"
/>
<AvatarFallback>U</AvatarFallback>
</Avatar>
)
}

View file

@ -3,6 +3,7 @@
import {
CodeIcon,
Laptop,
LogOut,
Moon,
Settings,
Sun
@ -12,7 +13,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
@ -28,6 +29,10 @@ import { KeymapType } from "@/lib/types"
import { cn } from "@/lib/utils"
import { useKeymapType } from "@/hooks/useKeymapType"
import { NEXT_PUBLIC_SOURCEBOT_VERSION } from "@/lib/environment.client";
import { useSession } from "next-auth/react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { signOut } from "next-auth/react"
interface SettingsDropdownProps {
menuButtonClassName?: string;
@ -38,7 +43,8 @@ export const SettingsDropdown = ({
}: SettingsDropdownProps) => {
const { theme: _theme, setTheme } = useTheme();
const [ keymapType, setKeymapType ] = useKeymapType();
const [keymapType, setKeymapType] = useKeymapType();
const { data: session } = useSession();
const theme = useMemo(() => {
return _theme ?? "light";
@ -64,9 +70,31 @@ export const SettingsDropdown = ({
<Settings className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>Settings</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuContent className="w-64">
{session?.user && (
<DropdownMenuGroup>
<div className="flex flex-row items-center gap-1 p-2">
<Avatar>
<AvatarImage
src={session.user.image ?? ""}
/>
<AvatarFallback>
{session.user.name && session.user.name.length > 0 ? session.user.name[0] : 'U'}
</AvatarFallback>
</Avatar>
<p className="text-sm font-medium text-ellipsis">{session.user.email ?? "User"}</p>
</div>
<DropdownMenuItem
onClick={() => {
signOut();
}}
>
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
</DropdownMenuGroup>
)}
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
@ -107,11 +135,11 @@ export const SettingsDropdown = ({
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<div className="px-2 py-1 text-sm text-muted-foreground">
version: {NEXT_PUBLIC_SOURCEBOT_VERSION}
</div>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<div className="px-2 py-1 text-sm text-muted-foreground">
version: {NEXT_PUBLIC_SOURCEBOT_VERSION}
</div>
</DropdownMenuContent>
</DropdownMenu>
)

View file

@ -1,11 +1,11 @@
import type { Metadata } from "next";
import "./globals.css";
import { ThemeProvider } from "next-themes";
import { Suspense } from "react";
import { QueryClientProvider } from "./queryClientProvider";
import { PHProvider } from "./posthogProvider";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { SessionProvider } from "next-auth/react";
export const metadata: Metadata = {
title: "Sourcebot",
@ -25,26 +25,22 @@ export default function RootLayout({
>
<body>
<Toaster />
<PHProvider>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<QueryClientProvider>
<TooltipProvider>
{/*
@todo : ideally we don't wrap everything in a suspense boundary.
@see : https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
*/}
<Suspense>
<SessionProvider>
<PHProvider>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<QueryClientProvider>
<TooltipProvider>
{children}
</Suspense>
</TooltipProvider>
</QueryClientProvider>
</ThemeProvider>
</PHProvider>
</TooltipProvider>
</QueryClientProvider>
</ThemeProvider>
</PHProvider>
</SessionProvider>
</body>
</html>
);

View file

@ -14,7 +14,7 @@ import { createPathWithQueryParams } from "@/lib/utils";
import { SymbolIcon } from "@radix-ui/react-icons";
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { ImperativePanelHandle } from "react-resizable-panels";
import { getRepos, search } from "../api/(client)/client";
import { TopBar } from "../components/topBar";
@ -25,6 +25,17 @@ import { SearchResultsPanel } from "./components/searchResultsPanel";
const DEFAULT_MAX_MATCH_DISPLAY_COUNT = 10000;
export default function SearchPage() {
// We need a suspense boundary here since we are accessing query params
// in the top level page.
// @see : https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
return (
<Suspense>
<SearchPageInternal />
</Suspense>
)
}
const SearchPageInternal = () => {
const router = useRouter();
const searchQuery = useNonEmptyQueryParam(SearchQueryParams.query) ?? "";
const _maxMatchDisplayCount = parseInt(useNonEmptyQueryParam(SearchQueryParams.maxMatchDisplayCount) ?? `${DEFAULT_MAX_MATCH_DISPLAY_COUNT}`);