Add scrolling to organization picker dropdown

Co-authored-by: Siumauricio <47042324+Siumauricio@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-12 15:49:50 +00:00
parent 5c45cfcefe
commit 6b2eedefd7
13 changed files with 129 additions and 126 deletions

View File

@@ -7,6 +7,7 @@ import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor"; import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -24,7 +25,6 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { api } from "@/utils/api"; import { api } from "@/utils/api";

View File

@@ -1,8 +1,8 @@
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import { badgeStateColor } from "@/components/dashboard/application/logs/show";
import { Badge } from "@/components/ui/badge";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { badgeStateColor } from "@/components/dashboard/application/logs/show";
import { Badge } from "@/components/ui/badge";
import { import {
Card, Card,
CardContent, CardContent,

View File

@@ -19,9 +19,9 @@ import {
import { import {
Form, Form,
FormControl, FormControl,
FormDescription,
FormField, FormField,
FormItem, FormItem,
FormDescription,
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";

View File

@@ -17,9 +17,9 @@ import {
import { import {
Form, Form,
FormControl, FormControl,
FormDescription,
FormField, FormField,
FormItem, FormItem,
FormDescription,
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";

View File

@@ -19,9 +19,9 @@ import {
import { import {
Form, Form,
FormControl, FormControl,
FormDescription,
FormField, FormField,
FormItem, FormItem,
FormDescription,
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";

View File

@@ -18,9 +18,9 @@ import {
import { import {
Form, Form,
FormControl, FormControl,
FormDescription,
FormField, FormField,
FormItem, FormItem,
FormDescription,
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";

View File

@@ -1,7 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useHealthCheckAfterMutation } from "@/hooks/use-health-check-after-mutation";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block"; import { AlertBlock } from "@/components/shared/alert-block";
@@ -24,6 +23,7 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { useHealthCheckAfterMutation } from "@/hooks/use-health-check-after-mutation";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
const schema = z.object({ const schema = z.object({

View File

@@ -1,7 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { ArrowRightLeft, Plus, Trash2 } from "lucide-react"; import { ArrowRightLeft, Plus, Trash2 } from "lucide-react";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { useHealthCheckAfterMutation } from "@/hooks/use-health-check-after-mutation";
import type React from "react"; import type React from "react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useFieldArray, useForm } from "react-hook-form"; import { useFieldArray, useForm } from "react-hook-form";
@@ -36,6 +35,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useHealthCheckAfterMutation } from "@/hooks/use-health-check-after-mutation";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
interface Props { interface Props {

View File

@@ -638,127 +638,129 @@ function SidebarLogo() {
<DropdownMenuLabel className="text-xs text-muted-foreground"> <DropdownMenuLabel className="text-xs text-muted-foreground">
Organizations Organizations
</DropdownMenuLabel> </DropdownMenuLabel>
{organizations?.map((org) => { <div className="max-h-[60vh] overflow-y-auto">
const isDefault = org.members?.[0]?.isDefault ?? false; {organizations?.map((org) => {
return ( const isDefault = org.members?.[0]?.isDefault ?? false;
<div return (
className="flex flex-row justify-between" <div
key={org.name} className="flex flex-row justify-between"
> key={org.name}
<DropdownMenuItem
onClick={async () => {
await authClient.organization.setActive({
organizationId: org.id,
});
window.location.reload();
}}
className="w-full gap-2 p-2"
> >
<div className="flex flex-col gap-1"> <DropdownMenuItem
<div className="flex items-center gap-2"> onClick={async () => {
{org.name} await authClient.organization.setActive({
</div>
</div>
<div className="flex size-6 items-center justify-center rounded-sm border">
<Logo
className={cn(
"transition-all",
state === "collapsed" ? "size-6" : "size-10",
)}
logoUrl={org.logo ?? undefined}
/>
</div>
</DropdownMenuItem>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
className={cn(
"group",
isDefault
? "hover:bg-yellow-500/10"
: "hover:bg-blue-500/10",
)}
isLoading={isSettingDefault && !isDefault}
disabled={isDefault}
onClick={async (e) => {
if (isDefault) return;
e.stopPropagation();
await setDefaultOrganization({
organizationId: org.id, organizationId: org.id,
}) });
.then(() => { window.location.reload();
refetch();
toast.success("Default organization updated");
})
.catch((error) => {
toast.error(
error?.message ||
"Error setting default organization",
);
});
}} }}
title={ className="w-full gap-2 p-2"
isDefault
? "Default organization"
: "Set as default"
}
> >
{isDefault ? ( <div className="flex flex-col gap-1">
<Star <div className="flex items-center gap-2">
fill="#eab308" {org.name}
stroke="#eab308" </div>
className="size-4 text-yellow-500" </div>
<div className="flex size-6 items-center justify-center rounded-sm border">
<Logo
className={cn(
"transition-all",
state === "collapsed" ? "size-6" : "size-10",
)}
logoUrl={org.logo ?? undefined}
/> />
) : ( </div>
<Star </DropdownMenuItem>
fill="none"
stroke="currentColor" <div className="flex items-center gap-2">
className="size-4 text-gray-400 group-hover:text-blue-500 transition-colors" <Button
/> variant="ghost"
)} size="icon"
</Button> className={cn(
{org.ownerId === session?.user?.id && ( "group",
<> isDefault
<AddOrganization organizationId={org.id} /> ? "hover:bg-yellow-500/10"
<DialogAction : "hover:bg-blue-500/10",
title="Delete Organization" )}
description="Are you sure you want to delete this organization?" isLoading={isSettingDefault && !isDefault}
type="destructive" disabled={isDefault}
onClick={async () => { onClick={async (e) => {
await deleteOrganization({ if (isDefault) return;
organizationId: org.id, e.stopPropagation();
await setDefaultOrganization({
organizationId: org.id,
})
.then(() => {
refetch();
toast.success("Default organization updated");
}) })
.then(() => { .catch((error) => {
refetch(); toast.error(
toast.success( error?.message ||
"Organization deleted successfully", "Error setting default organization",
); );
});
}}
title={
isDefault
? "Default organization"
: "Set as default"
}
>
{isDefault ? (
<Star
fill="#eab308"
stroke="#eab308"
className="size-4 text-yellow-500"
/>
) : (
<Star
fill="none"
stroke="currentColor"
className="size-4 text-gray-400 group-hover:text-blue-500 transition-colors"
/>
)}
</Button>
{org.ownerId === session?.user?.id && (
<>
<AddOrganization organizationId={org.id} />
<DialogAction
title="Delete Organization"
description="Are you sure you want to delete this organization?"
type="destructive"
onClick={async () => {
await deleteOrganization({
organizationId: org.id,
}) })
.catch((error) => { .then(() => {
toast.error( refetch();
error?.message || toast.success(
"Error deleting organization", "Organization deleted successfully",
); );
}); })
}} .catch((error) => {
> toast.error(
<Button error?.message ||
variant="ghost" "Error deleting organization",
size="icon" );
className="group hover:bg-red-500/10" });
isLoading={isRemoving} }}
> >
<Trash2 className="size-4 text-primary group-hover:text-red-500" /> <Button
</Button> variant="ghost"
</DialogAction> size="icon"
</> className="group hover:bg-red-500/10"
)} isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
</>
)}
</div>
</div> </div>
</div> );
); })}
})} </div>
{(user?.role === "owner" || {(user?.role === "owner" ||
user?.role === "admin" || user?.role === "admin" ||
isCloud) && ( isCloud) && (

View File

@@ -2,8 +2,8 @@
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { authClient } from "@/lib/auth-client";
export function SignInWithGithub() { export function SignInWithGithub() {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);

View File

@@ -2,8 +2,8 @@
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { authClient } from "@/lib/auth-client";
export function SignInWithGoogle() { export function SignInWithGoogle() {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);

View File

@@ -22,12 +22,12 @@ import { mountRouter } from "./routers/mount";
import { mysqlRouter } from "./routers/mysql"; import { mysqlRouter } from "./routers/mysql";
import { notificationRouter } from "./routers/notification"; import { notificationRouter } from "./routers/notification";
import { organizationRouter } from "./routers/organization"; import { organizationRouter } from "./routers/organization";
import { licenseKeyRouter } from "./routers/proprietary/license-key";
import { ssoRouter } from "./routers/proprietary/sso";
import { portRouter } from "./routers/port"; import { portRouter } from "./routers/port";
import { postgresRouter } from "./routers/postgres"; import { postgresRouter } from "./routers/postgres";
import { previewDeploymentRouter } from "./routers/preview-deployment"; import { previewDeploymentRouter } from "./routers/preview-deployment";
import { projectRouter } from "./routers/project"; import { projectRouter } from "./routers/project";
import { licenseKeyRouter } from "./routers/proprietary/license-key";
import { ssoRouter } from "./routers/proprietary/sso";
import { redirectsRouter } from "./routers/redirects"; import { redirectsRouter } from "./routers/redirects";
import { redisRouter } from "./routers/redis"; import { redisRouter } from "./routers/redis";
import { registryRouter } from "./routers/registry"; import { registryRouter } from "./routers/registry";

View File

@@ -1,8 +1,9 @@
import { exit } from "node:process";
import { exec } from "node:child_process"; import { exec } from "node:child_process";
import { exit } from "node:process";
import { promisify } from "node:util"; import { promisify } from "node:util";
const execAsync = promisify(exec); const execAsync = promisify(exec);
import { setupDirectories } from "@dokploy/server/setup/config-paths"; import { setupDirectories } from "@dokploy/server/setup/config-paths";
import { initializePostgres } from "@dokploy/server/setup/postgres-setup"; import { initializePostgres } from "@dokploy/server/setup/postgres-setup";
import { initializeRedis } from "@dokploy/server/setup/redis-setup"; import { initializeRedis } from "@dokploy/server/setup/redis-setup";