mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 20:35:24 +00:00
Move logout button & profile picture into settings dropdown (#172)
This commit is contained in:
parent
5d253ffa12
commit
a5091fb900
5 changed files with 65 additions and 67 deletions
|
|
@ -5,8 +5,6 @@ import { Separator } from "@/components/ui/separator";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import logoDark from "../../../public/sb_logo_dark_small.png";
|
import logoDark from "../../../public/sb_logo_dark_small.png";
|
||||||
import logoLight from "../../../public/sb_logo_light_small.png";
|
import logoLight from "../../../public/sb_logo_light_small.png";
|
||||||
import { ProfilePicture } from "./profilePicture";
|
|
||||||
import { signOut } from "@/auth";
|
|
||||||
import { SettingsDropdown } from "./settingsDropdown";
|
import { SettingsDropdown } from "./settingsDropdown";
|
||||||
import { GitHubLogoIcon, DiscordLogoIcon } from "@radix-ui/react-icons";
|
import { GitHubLogoIcon, DiscordLogoIcon } from "@radix-ui/react-icons";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
@ -88,21 +86,6 @@ export const NavigationMenu = async () => {
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
<SettingsDropdown />
|
<SettingsDropdown />
|
||||||
<form
|
|
||||||
action={async () => {
|
|
||||||
"use server";
|
|
||||||
await signOut();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="outline"
|
|
||||||
size="default"
|
|
||||||
>
|
|
||||||
Logout
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
<ProfilePicture />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import {
|
import {
|
||||||
CodeIcon,
|
CodeIcon,
|
||||||
Laptop,
|
Laptop,
|
||||||
|
LogOut,
|
||||||
Moon,
|
Moon,
|
||||||
Settings,
|
Settings,
|
||||||
Sun
|
Sun
|
||||||
|
|
@ -12,7 +13,7 @@ import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuGroup,
|
DropdownMenuGroup,
|
||||||
DropdownMenuLabel,
|
DropdownMenuItem,
|
||||||
DropdownMenuPortal,
|
DropdownMenuPortal,
|
||||||
DropdownMenuRadioGroup,
|
DropdownMenuRadioGroup,
|
||||||
DropdownMenuRadioItem,
|
DropdownMenuRadioItem,
|
||||||
|
|
@ -28,6 +29,10 @@ import { KeymapType } from "@/lib/types"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { useKeymapType } from "@/hooks/useKeymapType"
|
import { useKeymapType } from "@/hooks/useKeymapType"
|
||||||
import { NEXT_PUBLIC_SOURCEBOT_VERSION } from "@/lib/environment.client";
|
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 {
|
interface SettingsDropdownProps {
|
||||||
menuButtonClassName?: string;
|
menuButtonClassName?: string;
|
||||||
|
|
@ -38,7 +43,8 @@ export const SettingsDropdown = ({
|
||||||
}: SettingsDropdownProps) => {
|
}: SettingsDropdownProps) => {
|
||||||
|
|
||||||
const { theme: _theme, setTheme } = useTheme();
|
const { theme: _theme, setTheme } = useTheme();
|
||||||
const [ keymapType, setKeymapType ] = useKeymapType();
|
const [keymapType, setKeymapType] = useKeymapType();
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
const theme = useMemo(() => {
|
const theme = useMemo(() => {
|
||||||
return _theme ?? "light";
|
return _theme ?? "light";
|
||||||
|
|
@ -64,9 +70,31 @@ export const SettingsDropdown = ({
|
||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="w-56">
|
<DropdownMenuContent className="w-64">
|
||||||
<DropdownMenuLabel>Settings</DropdownMenuLabel>
|
{session?.user && (
|
||||||
<DropdownMenuSeparator />
|
<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>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuSub>
|
<DropdownMenuSub>
|
||||||
<DropdownMenuSubTrigger>
|
<DropdownMenuSubTrigger>
|
||||||
|
|
@ -107,11 +135,11 @@ export const SettingsDropdown = ({
|
||||||
</DropdownMenuSubContent>
|
</DropdownMenuSubContent>
|
||||||
</DropdownMenuPortal>
|
</DropdownMenuPortal>
|
||||||
</DropdownMenuSub>
|
</DropdownMenuSub>
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<div className="px-2 py-1 text-sm text-muted-foreground">
|
|
||||||
version: {NEXT_PUBLIC_SOURCEBOT_VERSION}
|
|
||||||
</div>
|
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<div className="px-2 py-1 text-sm text-muted-foreground">
|
||||||
|
version: {NEXT_PUBLIC_SOURCEBOT_VERSION}
|
||||||
|
</div>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { ThemeProvider } from "next-themes";
|
import { ThemeProvider } from "next-themes";
|
||||||
import { Suspense } from "react";
|
|
||||||
import { QueryClientProvider } from "./queryClientProvider";
|
import { QueryClientProvider } from "./queryClientProvider";
|
||||||
import { PHProvider } from "./posthogProvider";
|
import { PHProvider } from "./posthogProvider";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
|
import { SessionProvider } from "next-auth/react";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Sourcebot",
|
title: "Sourcebot",
|
||||||
|
|
@ -25,26 +25,22 @@ export default function RootLayout({
|
||||||
>
|
>
|
||||||
<body>
|
<body>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<PHProvider>
|
<SessionProvider>
|
||||||
<ThemeProvider
|
<PHProvider>
|
||||||
attribute="class"
|
<ThemeProvider
|
||||||
defaultTheme="system"
|
attribute="class"
|
||||||
enableSystem
|
defaultTheme="system"
|
||||||
disableTransitionOnChange
|
enableSystem
|
||||||
>
|
disableTransitionOnChange
|
||||||
<QueryClientProvider>
|
>
|
||||||
<TooltipProvider>
|
<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>
|
|
||||||
{children}
|
{children}
|
||||||
</Suspense>
|
</TooltipProvider>
|
||||||
</TooltipProvider>
|
</QueryClientProvider>
|
||||||
</QueryClientProvider>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
</PHProvider>
|
||||||
</PHProvider>
|
</SessionProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { createPathWithQueryParams } from "@/lib/utils";
|
||||||
import { SymbolIcon } from "@radix-ui/react-icons";
|
import { SymbolIcon } from "@radix-ui/react-icons";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useRouter } from "next/navigation";
|
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 { ImperativePanelHandle } from "react-resizable-panels";
|
||||||
import { getRepos, search } from "../api/(client)/client";
|
import { getRepos, search } from "../api/(client)/client";
|
||||||
import { TopBar } from "../components/topBar";
|
import { TopBar } from "../components/topBar";
|
||||||
|
|
@ -25,6 +25,17 @@ import { SearchResultsPanel } from "./components/searchResultsPanel";
|
||||||
const DEFAULT_MAX_MATCH_DISPLAY_COUNT = 10000;
|
const DEFAULT_MAX_MATCH_DISPLAY_COUNT = 10000;
|
||||||
|
|
||||||
export default function SearchPage() {
|
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 router = useRouter();
|
||||||
const searchQuery = useNonEmptyQueryParam(SearchQueryParams.query) ?? "";
|
const searchQuery = useNonEmptyQueryParam(SearchQueryParams.query) ?? "";
|
||||||
const _maxMatchDisplayCount = parseInt(useNonEmptyQueryParam(SearchQueryParams.maxMatchDisplayCount) ?? `${DEFAULT_MAX_MATCH_DISPLAY_COUNT}`);
|
const _maxMatchDisplayCount = parseInt(useNonEmptyQueryParam(SearchQueryParams.maxMatchDisplayCount) ?? `${DEFAULT_MAX_MATCH_DISPLAY_COUNT}`);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue