Browse Source

org setup wip

Milo Schwartz 6 months ago
parent
commit
d1e2b58c81

+ 62 - 45
server/routers/org/createOrg.ts

@@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from "express";
 import { z } from "zod";
 import { z } from "zod";
 import { db } from "@server/db";
 import { db } from "@server/db";
 import { eq } from "drizzle-orm";
 import { eq } from "drizzle-orm";
-import { orgs, roleActions, roles, userOrgs } from "@server/db/schema";
+import { Org, orgs, roleActions, roles, userOrgs } from "@server/db/schema";
 import response from "@server/utils/response";
 import response from "@server/utils/response";
 import HttpCode from "@server/types/HttpCode";
 import HttpCode from "@server/types/HttpCode";
 import createHttpError from "http-errors";
 import createHttpError from "http-errors";
@@ -15,7 +15,7 @@ import { defaultRoleAllowedActions } from "../role";
 const createOrgSchema = z
 const createOrgSchema = z
     .object({
     .object({
         orgId: z.string(),
         orgId: z.string(),
-        name: z.string().min(1).max(255),
+        name: z.string().min(1).max(255)
         // domain: z.string().min(1).max(255).optional(),
         // domain: z.string().min(1).max(255).optional(),
     })
     })
     .strict();
     .strict();
@@ -66,65 +66,82 @@ export async function createOrg(
             );
             );
         }
         }
 
 
-        // create a url from config.app.base_url and get the hostname
-        const domain = new URL(config.app.base_url).hostname;
+        let error = "";
+        let org: Org | null = null;
 
 
-        const newOrg = await db
-            .insert(orgs)
-            .values({
-                orgId,
-                name,
-                domain,
-            })
-            .returning();
+        await db.transaction(async (trx) => {
+            // create a url from config.app.base_url and get the hostname
+            const domain = new URL(config.app.base_url).hostname;
 
 
-        const roleId = await createAdminRole(newOrg[0].orgId);
+            const newOrg = await trx
+                .insert(orgs)
+                .values({
+                    orgId,
+                    name,
+                    domain
+                })
+                .returning();
 
 
-        if (!roleId) {
-            return next(
-                createHttpError(
-                    HttpCode.INTERNAL_SERVER_ERROR,
-                    `Error creating Admin role`
-                )
-            );
-        }
+            if (newOrg.length === 0) {
+                error = "Failed to create organization";
+                trx.rollback();
+                return;
+            }
+
+            org = newOrg[0];
+
+            const roleId = await createAdminRole(newOrg[0].orgId);
 
 
-        await db
-            .insert(userOrgs)
-            .values({
+            if (!roleId) {
+                error = "Failed to create Admin role";
+                trx.rollback();
+                return;
+            }
+
+            await trx.insert(userOrgs).values({
                 userId: req.user!.userId,
                 userId: req.user!.userId,
                 orgId: newOrg[0].orgId,
                 orgId: newOrg[0].orgId,
                 roleId: roleId,
                 roleId: roleId,
-                isOwner: true,
-            })
-            .execute();
-
-        const memberRole = await db
-            .insert(roles)
-            .values({
-                name: "Member",
-                description: "Members can only view resources",
-                orgId,
-            })
-            .returning();
-
-        await db
-            .insert(roleActions)
-            .values(
+                isOwner: true
+            });
+
+            const memberRole = await trx
+                .insert(roles)
+                .values({
+                    name: "Member",
+                    description: "Members can only view resources",
+                    orgId
+                })
+                .returning();
+
+            await trx.insert(roleActions).values(
                 defaultRoleAllowedActions.map((action) => ({
                 defaultRoleAllowedActions.map((action) => ({
                     roleId: memberRole[0].roleId,
                     roleId: memberRole[0].roleId,
                     actionId: action,
                     actionId: action,
-                    orgId,
+                    orgId
                 }))
                 }))
-            )
-            .execute();
+            );
+        });
+
+        if (!org) {
+            return next(
+                createHttpError(
+                    HttpCode.INTERNAL_SERVER_ERROR,
+                    "Failed to createo org"
+                )
+            );
+        }
+
+        if (error) {
+            return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, error));
+        }
 
 
         return response(res, {
         return response(res, {
-            data: newOrg[0],
+            data: org,
             success: true,
             success: true,
             error: false,
             error: false,
             message: "Organization created successfully",
             message: "Organization created successfully",
-            status: HttpCode.CREATED,
+            status: HttpCode.CREATED
         });
         });
     } catch (error) {
     } catch (error) {
         logger.error(error);
         logger.error(error);

+ 10 - 1
src/app/[orgId]/settings/components/Header.tsx

@@ -37,7 +37,7 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
 import { useToast } from "@app/hooks/useToast";
 import { useToast } from "@app/hooks/useToast";
 import { cn, formatAxiosError } from "@app/lib/utils";
 import { cn, formatAxiosError } from "@app/lib/utils";
 import { ListOrgsResponse } from "@server/routers/org";
 import { ListOrgsResponse } from "@server/routers/org";
-import { Check, ChevronsUpDown } from "lucide-react";
+import { Check, ChevronsUpDown, Plus } from "lucide-react";
 import Link from "next/link";
 import Link from "next/link";
 import { useRouter } from "next/navigation";
 import { useRouter } from "next/navigation";
 import { useState } from "react";
 import { useState } from "react";
@@ -180,6 +180,15 @@ export default function Header({ email, orgId, name, orgs }: HeaderProps) {
                                 </CommandEmpty>
                                 </CommandEmpty>
                                 <CommandGroup className="[50px]">
                                 <CommandGroup className="[50px]">
                                     <CommandList>
                                     <CommandList>
+                                        <CommandItem
+                                            className="flex items-center border border-input mb-2 cursor-pointer"
+                                            onSelect={(currentValue) => {
+                                                router.push("/setup");
+                                            }}
+                                        >
+                                            <Plus className="mr-2 h-4 w-4"/>
+                                            New Organization
+                                        </CommandItem>
                                         {orgs.map((org) => (
                                         {orgs.map((org) => (
                                             <CommandItem
                                             <CommandItem
                                                 key={org.orgId}
                                                 key={org.orgId}

+ 10 - 8
src/app/[orgId]/settings/layout.tsx

@@ -96,18 +96,20 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
 
 
     return (
     return (
         <>
         <>
-            <div className="w-full border-b bg-neutral-100 dark:bg-neutral-800 mb-6 select-none sm:px-0 px-3 pt-3">
-                <div className="container mx-auto flex flex-col content-between gap-4 ">
-                    <Header
-                        email={user.email}
-                        orgId={params.orgId}
-                        orgs={orgs}
-                    />
+            <div className="w-full border-b bg-neutral-100 dark:bg-neutral-800 select-none sm:px-0 px-3 fixed top-0 z-10">
+                <div className="container mx-auto flex flex-col content-between">
+                    <div className="my-4">
+                        <Header
+                            email={user.email}
+                            orgId={params.orgId}
+                            orgs={orgs}
+                        />
+                    </div>
                     <TopbarNav items={topNavItems} orgId={params.orgId} />
                     <TopbarNav items={topNavItems} orgId={params.orgId} />
                 </div>
                 </div>
             </div>
             </div>
 
 
-            <div className="container mx-auto sm:px-0 px-3">{children}</div>
+            <div className="container mx-auto sm:px-0 px-3 pt-[165px]">{children}</div>
 
 
             <footer className="w-full mt-6 py-3">
             <footer className="w-full mt-6 py-3">
                 <div className="container mx-auto flex justify-end items-center px-3 sm:px-0 text-sm text-neutral-300 dark:text-neutral-700 space-x-3 select-none">
                 <div className="container mx-auto flex justify-end items-center px-3 sm:px-0 text-sm text-neutral-300 dark:text-neutral-700 space-x-3 select-none">

+ 1 - 1
src/app/[orgId]/settings/share-links/components/CreateShareLinkForm.tsx

@@ -285,7 +285,7 @@ export default function CreateShareLinkForm({
                                                                                 r
                                                                                 r
                                                                             ) => (
                                                                             ) => (
                                                                                 <CommandItem
                                                                                 <CommandItem
-                                                                                    value={r.resourceId.toString()}
+                                                                                    value={r.name}
                                                                                     key={
                                                                                     key={
                                                                                         r.resourceId
                                                                                         r.resourceId
                                                                                     }
                                                                                     }

+ 7 - 6
src/app/globals.css

@@ -6,11 +6,11 @@
 @layer base {
 @layer base {
   :root {
   :root {
     --background: 0 0% 100%;
     --background: 0 0% 100%;
-    --foreground: 0 0.0% 10.0%;
+    --foreground: 20 5.0% 10.0%;
     --card: 0 0% 100%;
     --card: 0 0% 100%;
-    --card-foreground: 0 0% 100%;
+    --card-foreground: 20 5.0% 10.0%;
     --popover: 0 0% 100%;
     --popover: 0 0% 100%;
-    --popover-foreground: 0 0% 100%;
+    --popover-foreground: 20 5.0% 10.0%;
     --primary: 24.6 95% 53.1%;
     --primary: 24.6 95% 53.1%;
     --primary-foreground: 60 9.1% 97.8%;
     --primary-foreground: 60 9.1% 97.8%;
     --secondary: 60 4.8% 95.9%;
     --secondary: 60 4.8% 95.9%;
@@ -33,11 +33,11 @@
   }
   }
 
 
   .dark {
   .dark {
-    --background: 0 0.0% 10.0%;
+    --background: 20 5.0% 10.0%;
     --foreground: 60 9.1% 97.8%;
     --foreground: 60 9.1% 97.8%;
-    --card: 0 0.0% 10.0%;
+    --card: 20 5.0% 10.0%;
     --card-foreground: 60 9.1% 97.8%;
     --card-foreground: 60 9.1% 97.8%;
-    --popover: 0 0.0% 10.0%;
+    --popover: 20 5.0% 10.0%;
     --popover-foreground: 60 9.1% 97.8%;
     --popover-foreground: 60 9.1% 97.8%;
     --primary: 20.5 90.2% 48.2%;
     --primary: 20.5 90.2% 48.2%;
     --primary-foreground: 60 9.1% 97.8%;
     --primary-foreground: 60 9.1% 97.8%;
@@ -70,3 +70,4 @@
         @apply bg-background text-foreground;
         @apply bg-background text-foreground;
     }
     }
 }
 }
+

+ 3 - 3
src/app/setup/layout.tsx

@@ -5,13 +5,13 @@ import { cache } from "react";
 
 
 export const metadata: Metadata = {
 export const metadata: Metadata = {
     title: `Setup - Pangolin`,
     title: `Setup - Pangolin`,
-    description: "",
+    description: ""
 };
 };
 
 
 export const dynamic = "force-dynamic";
 export const dynamic = "force-dynamic";
 
 
 export default async function SetupLayout({
 export default async function SetupLayout({
-    children,
+    children
 }: {
 }: {
     children: React.ReactNode;
     children: React.ReactNode;
 }) {
 }) {
@@ -22,5 +22,5 @@ export default async function SetupLayout({
         redirect("/?redirect=/setup");
         redirect("/?redirect=/setup");
     }
     }
 
 
-    return <div className="mt-32">{children}</div>;
+    return <div className="w-full max-w-2xl mx-auto p-3 md:mt-32">{children}</div>;
 }
 }

+ 185 - 151
src/app/setup/page.tsx

@@ -11,104 +11,112 @@ import {
     CardContent,
     CardContent,
     CardDescription,
     CardDescription,
     CardHeader,
     CardHeader,
-    CardTitle,
+    CardTitle
 } from "@app/components/ui/card";
 } from "@app/components/ui/card";
 import CopyTextBox from "@app/components/CopyTextBox";
 import CopyTextBox from "@app/components/CopyTextBox";
 import { formatAxiosError } from "@app/lib/utils";
 import { formatAxiosError } from "@app/lib/utils";
 import { createApiClient } from "@app/api";
 import { createApiClient } from "@app/api";
 import { useEnvContext } from "@app/hooks/useEnvContext";
 import { useEnvContext } from "@app/hooks/useEnvContext";
+import { Separator } from "@/components/ui/separator";
+import { z } from "zod";
+import { useRouter } from "next/navigation";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import {
+    Form,
+    FormControl,
+    FormDescription,
+    FormField,
+    FormItem,
+    FormLabel,
+    FormMessage
+} from "@app/components/ui/form";
+import { Alert, AlertDescription } from "@app/components/ui/alert";
 
 
 type Step = "org" | "site" | "resources";
 type Step = "org" | "site" | "resources";
 
 
+const orgSchema = z.object({
+    orgName: z.string().min(1, { message: "Organization name is required" }),
+    orgId: z.string().min(1, { message: "Organization ID is required" })
+});
+
 export default function StepperForm() {
 export default function StepperForm() {
     const [currentStep, setCurrentStep] = useState<Step>("org");
     const [currentStep, setCurrentStep] = useState<Step>("org");
-    const [orgName, setOrgName] = useState("");
-    const [orgId, setOrgId] = useState("");
-    const [siteName, setSiteName] = useState("");
-    const [resourceName, setResourceName] = useState("");
-    const [orgCreated, setOrgCreated] = useState(false);
     const [orgIdTaken, setOrgIdTaken] = useState(false);
     const [orgIdTaken, setOrgIdTaken] = useState(false);
 
 
+    const [loading, setLoading] = useState(false);
+    const [error, setError] = useState<string | null>(null);
+
+    const orgForm = useForm<z.infer<typeof orgSchema>>({
+        resolver: zodResolver(orgSchema),
+        defaultValues: {
+            orgName: "",
+            orgId: ""
+        }
+    });
+
     const api = createApiClient(useEnvContext());
     const api = createApiClient(useEnvContext());
+    const router = useRouter();
 
 
     const checkOrgIdAvailability = useCallback(async (value: string) => {
     const checkOrgIdAvailability = useCallback(async (value: string) => {
         try {
         try {
             const res = await api.get(`/org/checkId`, {
             const res = await api.get(`/org/checkId`, {
                 params: {
                 params: {
-                    orgId: value,
-                },
+                    orgId: value
+                }
             });
             });
             setOrgIdTaken(res.status !== 404);
             setOrgIdTaken(res.status !== 404);
         } catch (error) {
         } catch (error) {
-            console.error("Error checking org ID availability:", error);
             setOrgIdTaken(false);
             setOrgIdTaken(false);
         }
         }
     }, []);
     }, []);
 
 
     const debouncedCheckOrgIdAvailability = useCallback(
     const debouncedCheckOrgIdAvailability = useCallback(
         debounce(checkOrgIdAvailability, 300),
         debounce(checkOrgIdAvailability, 300),
-        [checkOrgIdAvailability],
+        [checkOrgIdAvailability]
     );
     );
 
 
-    useEffect(() => {
-        if (orgId) {
-            debouncedCheckOrgIdAvailability(orgId);
-        }
-    }, [orgId, debouncedCheckOrgIdAvailability]);
+    const generateId = (name: string) => {
+        return name.toLowerCase().replace(/\s+/g, "-");
+    };
 
 
-    const showOrgIdError = () => {
+    async function orgSubmit(values: z.infer<typeof orgSchema>) {
         if (orgIdTaken) {
         if (orgIdTaken) {
-            return (
-                <p className="text-sm text-red-500">
-                    This ID is already taken. Please choose another.
-                </p>
-            );
+            return;
         }
         }
-        return null;
-    };
 
 
-    const generateId = (name: string) => {
-        return name.toLowerCase().replace(/\s+/g, "-");
-    };
+        setLoading(true);
 
 
-    const handleNext = async () => {
-        if (currentStep === "org") {
-            const res = await api
-                .put(`/org`, {
-                    orgId: orgId,
-                    name: orgName,
-                })
-                .catch((e) => {
-                    toast({
-                        variant: "destructive",
-                        title: "Error creating org",
-                        description: formatAxiosError(e),
-                    });
-                });
+        try {
+            const res = await api.put(`/org`, {
+                orgId: values.orgId,
+                name: values.orgName
+            });
 
 
             if (res && res.status === 201) {
             if (res && res.status === 201) {
                 setCurrentStep("site");
                 setCurrentStep("site");
-                setOrgCreated(true);
             }
             }
-        } else if (currentStep === "site") setCurrentStep("resources");
-    };
+        } catch (e) {
+            console.error(e);
+            setError(
+                formatAxiosError(e, "An error occurred while creating org")
+            );
+        }
 
 
-    const handlePrevious = () => {
-        if (currentStep === "site") setCurrentStep("org");
-        else if (currentStep === "resources") setCurrentStep("site");
-    };
+        setLoading(false);
+    }
 
 
     return (
     return (
         <>
         <>
-            <Card className="w-full max-w-2xl mx-auto">
+            <Card>
                 <CardHeader>
                 <CardHeader>
-                    <CardTitle>Setup Your Environment</CardTitle>
+                    <CardTitle>Setup</CardTitle>
                     <CardDescription>
                     <CardDescription>
-                        Create your organization, site, and resources.
+                        Create your organization, site, and resources
                     </CardDescription>
                     </CardDescription>
                 </CardHeader>
                 </CardHeader>
                 <CardContent>
                 <CardContent>
-                    <div className="mb-8">
+                    <section className="space-y-6">
                         <div className="flex justify-between mb-2">
                         <div className="flex justify-between mb-2">
                             <div className="flex flex-col items-center">
                             <div className="flex flex-col items-center">
                                 <div
                                 <div
@@ -171,108 +179,134 @@ export default function StepperForm() {
                                 </span>
                                 </span>
                             </div>
                             </div>
                         </div>
                         </div>
-                        <div className="flex items-center">
-                            <div className="flex-1 h-px bg-border"></div>
-                            <div className="flex-1 h-px bg-border"></div>
-                        </div>
-                    </div>
-                    {currentStep === "org" && (
-                        <div className="space-y-4">
-                            <div className="space-y-2">
-                                <Label htmlFor="orgName">
-                                    Organization Name
-                                </Label>
-                                <Input
-                                    id="orgName"
-                                    value={orgName}
-                                    onChange={(e) => {
-                                        setOrgName(e.target.value);
-                                        setOrgId(generateId(e.target.value));
+
+                        <Separator />
+
+                        {currentStep === "org" && (
+                            <Form {...orgForm}>
+                                <form
+                                    onSubmit={orgForm.handleSubmit(orgSubmit)}
+                                    className="space-y-8"
+                                >
+                                    <FormField
+                                        control={orgForm.control}
+                                        name="orgName"
+                                        render={({ field }) => (
+                                            <FormItem>
+                                                <FormLabel>
+                                                    Organization Name
+                                                </FormLabel>
+                                                <FormControl>
+                                                    <Input
+                                                        placeholder="Name your new organization"
+                                                        type="text"
+                                                        {...field}
+                                                        onChange={(e) => {
+                                                            const orgId =
+                                                                generateId(
+                                                                    e.target
+                                                                        .value
+                                                                );
+                                                            orgForm.setValue(
+                                                                "orgId",
+                                                                orgId
+                                                            );
+                                                            orgForm.setValue(
+                                                                "orgName",
+                                                                e.target.value
+                                                            );
+                                                            debouncedCheckOrgIdAvailability(
+                                                                orgId
+                                                            );
+                                                        }}
+                                                    />
+                                                </FormControl>
+                                                <FormMessage />
+                                                <FormDescription>
+                                                    This is the display name for
+                                                    your organization.
+                                                </FormDescription>
+                                            </FormItem>
+                                        )}
+                                    />
+                                    <FormField
+                                        control={orgForm.control}
+                                        name="orgId"
+                                        render={({ field }) => (
+                                            <FormItem>
+                                                <FormLabel>
+                                                    Organization ID
+                                                </FormLabel>
+                                                <FormControl>
+                                                    <Input
+                                                        type="text"
+                                                        placeholder="Enter unique organization ID"
+                                                        {...field}
+                                                    />
+                                                </FormControl>
+                                                <FormMessage />
+                                                <FormDescription>
+                                                    This is the unique
+                                                    identifier for your
+                                                    organization. This is
+                                                    separate from the display
+                                                    name.
+                                                </FormDescription>
+                                            </FormItem>
+                                        )}
+                                    />
+
+                                    {orgIdTaken && (
+                                        <Alert variant="destructive">
+                                            <AlertDescription>
+                                                Organization ID is already
+                                                taken. Please choose a different
+                                                one.
+                                            </AlertDescription>
+                                        </Alert>
+                                    )}
+
+                                    {error && (
+                                        <Alert variant="destructive">
+                                            <AlertDescription>
+                                                {error}
+                                            </AlertDescription>
+                                        </Alert>
+                                    )}
+
+                                    <div className="flex justify-end">
+                                        <Button
+                                            type="submit"
+                                            loading={loading}
+                                            disabled={
+                                                error !== null ||
+                                                loading ||
+                                                orgIdTaken
+                                            }
+                                        >
+                                            Create Organization
+                                        </Button>
+                                    </div>
+                                </form>
+                            </Form>
+                        )}
+
+                        {currentStep === "site" && (
+                            <div className="flex justify-end">
+                                <Button
+                                    type="submit"
+                                    variant="outline"
+                                    onClick={() => {
+                                        router.push(
+                                            `/${orgForm.getValues().orgId}/settings/sites`
+                                        );
                                     }}
                                     }}
-                                    placeholder="Enter organization name"
-                                    required
-                                />
-                            </div>
-                            <div className="space-y-2">
-                                <Label htmlFor="orgId">Organization ID</Label>
-                                <Input
-                                    id="orgId"
-                                    value={orgId}
-                                    onChange={(e) => setOrgId(e.target.value)}
-                                />
-                                {showOrgIdError()}
-                                <p className="text-sm text-muted-foreground">
-                                    This ID is automatically generated from the
-                                    organization name and must be unique.
-                                </p>
-                            </div>
-                        </div>
-                    )}
-                    {currentStep === "site" && (
-                        <div className="space-y-8">
-                            <div className="space-y-2">
-                                <Label htmlFor="siteName">Site Name</Label>
-                                <Input
-                                    id="siteName"
-                                    value={siteName}
-                                    onChange={(e) =>
-                                        setSiteName(e.target.value)
-                                    }
-                                    placeholder="Enter site name"
-                                    required
-                                />
-                            </div>
-                        </div>
-                    )}
-                    {currentStep === "resources" && (
-                        <div className="space-y-8">
-                            <div className="space-y-2">
-                                <Label htmlFor="resourceName">
-                                    Resource Name
-                                </Label>
-                                <Input
-                                    id="resourceName"
-                                    value={resourceName}
-                                    onChange={(e) =>
-                                        setResourceName(e.target.value)
-                                    }
-                                    placeholder="Enter resource name"
-                                    required
-                                />
-                            </div>
-                        </div>
-                    )}
-                    <div className="flex justify-between pt-4">
-                        <Button
-                            type="button"
-                            variant="outline"
-                            onClick={handlePrevious}
-                            disabled={
-                                currentStep === "org" ||
-                                (currentStep === "site" && orgCreated)
-                            }
-                        >
-                            Previous
-                        </Button>
-                        <div className="flex items-center space-x-2">
-                            {currentStep !== "org" ? (
-                                <Link
-                                    href={`/${orgId}/settings/sites`}
-                                    className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
                                 >
                                 >
                                     Skip for now
                                     Skip for now
-                                </Link>
-                            ) : null}
-
-                            <Button
-                                type="button"
-                                id="button"
-                                onClick={handleNext}
-                            >
-                                Create
-                            </Button>
-                        </div>
-                    </div>
+                                </Button>
+                            </div>
+                        )}
+                    </section>
                 </CardContent>
                 </CardContent>
             </Card>
             </Card>
         </>
         </>
@@ -281,7 +315,7 @@ export default function StepperForm() {
 
 
 function debounce<T extends (...args: any[]) => any>(
 function debounce<T extends (...args: any[]) => any>(
     func: T,
     func: T,
-    wait: number,
+    wait: number
 ): (...args: Parameters<T>) => void {
 ): (...args: Parameters<T>) => void {
     let timeout: NodeJS.Timeout | null = null;
     let timeout: NodeJS.Timeout | null = null;
 
 

+ 1 - 1
src/components/ui/command.tsx

@@ -15,7 +15,7 @@ const Command = React.forwardRef<
   <CommandPrimitive
   <CommandPrimitive
     ref={ref}
     ref={ref}
     className={cn(
     className={cn(
-      "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-foreground",
+      "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
       className
       className
     )}
     )}
     {...props}
     {...props}

+ 1 - 1
src/components/ui/dropdown-menu.tsx

@@ -47,7 +47,7 @@ const DropdownMenuSubContent = React.forwardRef<
   <DropdownMenuPrimitive.SubContent
   <DropdownMenuPrimitive.SubContent
     ref={ref}
     ref={ref}
     className={cn(
     className={cn(
-      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
       className
       className
     )}
     )}
     {...props}
     {...props}

+ 1 - 1
src/components/ui/popover.tsx

@@ -19,7 +19,7 @@ const PopoverContent = React.forwardRef<
       align={align}
       align={align}
       sideOffset={sideOffset}
       sideOffset={sideOffset}
       className={cn(
       className={cn(
-        "z-50 w-72 rounded-md border bg-popover p-4 text-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
         className
         className
       )}
       )}
       {...props}
       {...props}

+ 1 - 1
src/components/ui/select.tsx

@@ -75,7 +75,7 @@ const SelectContent = React.forwardRef<
     <SelectPrimitive.Content
     <SelectPrimitive.Content
       ref={ref}
       ref={ref}
       className={cn(
       className={cn(
-        "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
         position === "popper" &&
         position === "popper" &&
           "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
           "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
         className
         className