浏览代码

use bottom sheet instead of vaul drawer

Milo Schwartz 7 月之前
父节点
当前提交
e6263567a9

+ 2 - 2
Dockerfile

@@ -4,7 +4,7 @@ WORKDIR /app
 
 COPY package.json ./
 
-RUN npm install --legacy-peer-deps
+RUN npm install
 
 COPY . .
 
@@ -20,7 +20,7 @@ WORKDIR /app
 
 COPY package.json ./
 
-RUN npm install --omit=dev --legacy-peer-deps
+RUN npm install --omit=dev
 
 COPY --from=builder /app/.next ./.next
 COPY --from=builder /app/dist ./dist

+ 1 - 1
server/apiServer.ts

@@ -1,4 +1,4 @@
-import express, { Request, Response } from "express";
+import express from "express";
 import cors from "cors";
 import cookieParser from "cookie-parser";
 import config from "@server/config";

+ 86 - 78
server/config.ts

@@ -14,7 +14,10 @@ const portSchema = z.number().positive().gt(0).lte(65535);
 
 const environmentSchema = z.object({
     app: z.object({
-        base_url: z.string().url().transform((url) => url.toLowerCase()),
+        base_url: z
+            .string()
+            .url()
+            .transform((url) => url.toLowerCase()),
         log_level: z.enum(["debug", "info", "warn", "error"]),
         save_logs: z.boolean()
     }),
@@ -76,97 +79,102 @@ const environmentSchema = z.object({
         .optional()
 });
 
-const loadConfig = (configPath: string) => {
-    try {
-        const yamlContent = fs.readFileSync(configPath, "utf8");
-        const config = yaml.load(yamlContent);
-        return config;
-    } catch (error) {
-        if (error instanceof Error) {
-            throw new Error(
-                `Error loading configuration file: ${error.message}`
-            );
-        }
-        throw error;
-    }
-};
-
-const configFilePath1 = path.join(APP_PATH, "config.yml");
-const configFilePath2 = path.join(APP_PATH, "config.yaml");
-
-let environment: any;
-if (fs.existsSync(configFilePath1)) {
-    environment = loadConfig(configFilePath1);
-} else if (fs.existsSync(configFilePath2)) {
-    environment = loadConfig(configFilePath2);
-}
-if (!environment) {
-    const exampleConfigPath = path.join(__DIRNAME, "config.example.yml");
-    if (fs.existsSync(exampleConfigPath)) {
+export function getConfig() {
+    const loadConfig = (configPath: string) => {
         try {
-            const exampleConfigContent = fs.readFileSync(
-                exampleConfigPath,
-                "utf8"
-            );
-            fs.writeFileSync(configFilePath1, exampleConfigContent, "utf8");
-            environment = loadConfig(configFilePath1);
+            const yamlContent = fs.readFileSync(configPath, "utf8");
+            const config = yaml.load(yamlContent);
+            return config;
         } catch (error) {
             if (error instanceof Error) {
                 throw new Error(
-                    `Error creating configuration file from example: ${error.message}`
+                    `Error loading configuration file: ${error.message}`
                 );
             }
             throw error;
         }
-    } else {
-        throw new Error(
-            "No configuration file found and no example configuration available"
-        );
+    };
+
+    const configFilePath1 = path.join(APP_PATH, "config.yml");
+    const configFilePath2 = path.join(APP_PATH, "config.yaml");
+
+    let environment: any;
+    if (fs.existsSync(configFilePath1)) {
+        environment = loadConfig(configFilePath1);
+    } else if (fs.existsSync(configFilePath2)) {
+        environment = loadConfig(configFilePath2);
+    }
+    if (!environment) {
+        const exampleConfigPath = path.join(__DIRNAME, "config.example.yml");
+        if (fs.existsSync(exampleConfigPath)) {
+            try {
+                const exampleConfigContent = fs.readFileSync(
+                    exampleConfigPath,
+                    "utf8"
+                );
+                fs.writeFileSync(configFilePath1, exampleConfigContent, "utf8");
+                environment = loadConfig(configFilePath1);
+            } catch (error) {
+                if (error instanceof Error) {
+                    throw new Error(
+                        `Error creating configuration file from example: ${error.message}`
+                    );
+                }
+                throw error;
+            }
+        } else {
+            throw new Error(
+                "No configuration file found and no example configuration available"
+            );
+        }
     }
-}
 
-if (!environment) {
-    throw new Error("No configuration file found");
-}
+    if (!environment) {
+        throw new Error("No configuration file found");
+    }
 
-const parsedConfig = environmentSchema.safeParse(environment);
+    const parsedConfig = environmentSchema.safeParse(environment);
 
-if (!parsedConfig.success) {
-    const errors = fromError(parsedConfig.error);
-    throw new Error(`Invalid configuration file: ${errors}`);
-}
+    if (!parsedConfig.success) {
+        const errors = fromError(parsedConfig.error);
+        throw new Error(`Invalid configuration file: ${errors}`);
+    }
 
-const packageJsonPath = path.join(__DIRNAME, "..", "package.json");
-let packageJson: any;
-if (fs.existsSync && fs.existsSync(packageJsonPath)) {
-    const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
-    packageJson = JSON.parse(packageJsonContent);
+    const packageJsonPath = path.join(__DIRNAME, "..", "package.json");
+    let packageJson: any;
+    if (fs.existsSync && fs.existsSync(packageJsonPath)) {
+        const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
+        packageJson = JSON.parse(packageJsonContent);
 
-    if (packageJson.version) {
-        process.env.APP_VERSION = packageJson.version;
+        if (packageJson.version) {
+            process.env.APP_VERSION = packageJson.version;
+        }
     }
-}
 
-process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
-process.env.SERVER_EXTERNAL_PORT =
-    parsedConfig.data.server.external_port.toString();
-process.env.SERVER_INTERNAL_PORT =
-    parsedConfig.data.server.internal_port.toString();
-process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags
-    ?.require_email_verification
-    ? "true"
-    : "false";
-process.env.SESSION_COOKIE_NAME = parsedConfig.data.server.session_cookie_name;
-process.env.RESOURCE_SESSION_COOKIE_NAME =
-    parsedConfig.data.server.resource_session_cookie_name;
-process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false";
-process.env.DISABLE_SIGNUP_WITHOUT_INVITE = parsedConfig.data.flags
-    ?.disable_signup_without_invite
-    ? "true"
-    : "false";
-process.env.DISABLE_USER_CREATE_ORG = parsedConfig.data.flags
-    ?.disable_user_create_org
-    ? "true"
-    : "false";
+    process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
+    process.env.SERVER_EXTERNAL_PORT =
+        parsedConfig.data.server.external_port.toString();
+    process.env.SERVER_INTERNAL_PORT =
+        parsedConfig.data.server.internal_port.toString();
+    process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags
+        ?.require_email_verification
+        ? "true"
+        : "false";
+    process.env.SESSION_COOKIE_NAME =
+        parsedConfig.data.server.session_cookie_name;
+    process.env.RESOURCE_SESSION_COOKIE_NAME =
+        parsedConfig.data.server.resource_session_cookie_name;
+    process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false";
+    process.env.DISABLE_SIGNUP_WITHOUT_INVITE = parsedConfig.data.flags
+        ?.disable_signup_without_invite
+        ? "true"
+        : "false";
+    process.env.DISABLE_USER_CREATE_ORG = parsedConfig.data.flags
+        ?.disable_user_create_org
+        ? "true"
+        : "false";
+
+    return parsedConfig.data;
+}
 
-export default parsedConfig.data;
+export default getConfig();

+ 1 - 1
server/emails/templates/SendInviteLink.tsx

@@ -68,7 +68,7 @@ export const SendInviteLink = ({
                         <Section className="text-center my-6">
                             <Button
                                 href={inviteLink}
-                                className="rounded-lg bg-primary px-[12px] py-[9px] text-center font-semibold text-white cursor-pointer"
+                                className="rounded-lg bg-primary px-[12px] py-[9px] text-center font-semibold text-white cursor-pointer text-xl"
                             >
                                 Accept invitation to {orgName}
                             </Button>

+ 9 - 6
src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx

@@ -421,6 +421,7 @@ export default function ResourceAuthenticationPage() {
                                     <FormItem className="flex flex-col items-start">
                                         <FormLabel>Roles</FormLabel>
                                         <FormControl>
+                                            {/* @ts-ignore */}
                                             <TagInput
                                                 {...field}
                                                 activeTagIndex={
@@ -454,9 +455,9 @@ export default function ResourceAuthenticationPage() {
                                                     tag: {
                                                         body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
                                                     },
-                                                    input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
+                                                    input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
                                                     inlineTagsContainer:
-                                                        "bg-transparent"
+                                                        "bg-transparent p-2"
                                                 }}
                                             />
                                         </FormControl>
@@ -476,6 +477,7 @@ export default function ResourceAuthenticationPage() {
                                     <FormItem className="flex flex-col items-start">
                                         <FormLabel>Users</FormLabel>
                                         <FormControl>
+                                            {/* @ts-ignore */}
                                             <TagInput
                                                 {...field}
                                                 activeTagIndex={
@@ -509,9 +511,9 @@ export default function ResourceAuthenticationPage() {
                                                     tag: {
                                                         body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
                                                     },
-                                                    input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
+                                                    input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
                                                     inlineTagsContainer:
-                                                        "bg-transparent"
+                                                        "bg-transparent p-2"
                                                 }}
                                             />
                                         </FormControl>
@@ -649,6 +651,7 @@ export default function ResourceAuthenticationPage() {
                                                         Whitelisted Emails
                                                     </FormLabel>
                                                     <FormControl>
+                                                        {/* @ts-ignore */}
                                                         <TagInput
                                                             {...field}
                                                             activeTagIndex={
@@ -691,9 +694,9 @@ export default function ResourceAuthenticationPage() {
                                                                 tag: {
                                                                     body: "bg-muted hover:bg-accent text-foreground py-2 px-3 rounded-full"
                                                                 },
-                                                                input: "border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
+                                                                input: "text-base md:text-sm border-none bg-transparent text-inherit placeholder:text-inherit shadow-none",
                                                                 inlineTagsContainer:
-                                                                    "bg-transparent"
+                                                                    "bg-transparent p-2"
                                                             }}
                                                         />
                                                     </FormControl>

+ 1 - 1
src/app/[orgId]/settings/sites/[niceId]/layout.tsx

@@ -50,7 +50,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
                 <Breadcrumb>
                     <BreadcrumbList>
                         <BreadcrumbItem>
-                            <Link href="../../">Sites</Link>
+                            <Link href="../">Sites</Link>
                         </BreadcrumbItem>
                         <BreadcrumbSeparator />
                         <BreadcrumbItem>

+ 6 - 6
src/app/globals.css

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

+ 28 - 16
src/components/Credenza.tsx

@@ -24,6 +24,15 @@ import {
     DrawerTitle,
     DrawerTrigger
 } from "@/components/ui/drawer";
+import {
+    Sheet,
+    SheetContent,
+    SheetDescription,
+    SheetFooter,
+    SheetHeader,
+    SheetTitle,
+    SheetTrigger
+} from "./ui/sheet";
 
 interface BaseProps {
     children: React.ReactNode;
@@ -44,7 +53,7 @@ const desktop = "(min-width: 768px)";
 const Credenza = ({ children, ...props }: RootCredenzaProps) => {
     const isDesktop = useMediaQuery(desktop);
     // const isDesktop = true;
-    const Credenza = isDesktop ? Dialog : Drawer;
+    const Credenza = isDesktop ? Dialog : Sheet;
 
     return <Credenza {...props}>{children}</Credenza>;
 };
@@ -53,7 +62,7 @@ const CredenzaTrigger = ({ className, children, ...props }: CredenzaProps) => {
     const isDesktop = useMediaQuery(desktop);
     // const isDesktop = true;
 
-    const CredenzaTrigger = isDesktop ? DialogTrigger : DrawerTrigger;
+    const CredenzaTrigger = isDesktop ? DialogTrigger : SheetTrigger;
 
     return (
         <CredenzaTrigger className={className} {...props}>
@@ -79,10 +88,14 @@ const CredenzaContent = ({ className, children, ...props }: CredenzaProps) => {
     const isDesktop = useMediaQuery(desktop);
     // const isDesktop = true;
 
-    const CredenzaContent = isDesktop ? DialogContent : DrawerContent;
+    const CredenzaContent = isDesktop ? DialogContent : SheetContent;
 
     return (
-        <CredenzaContent className={className} {...props}>
+        <CredenzaContent
+            className={cn("overflow-y-auto max-h-screen", className)}
+            {...props}
+            side={"bottom"}
+        >
             {children}
         </CredenzaContent>
     );
@@ -98,7 +111,7 @@ const CredenzaDescription = ({
 
     const CredenzaDescription = isDesktop
         ? DialogDescription
-        : DrawerDescription;
+        : SheetDescription;
 
     return (
         <CredenzaDescription className={className} {...props}>
@@ -111,7 +124,7 @@ const CredenzaHeader = ({ className, children, ...props }: CredenzaProps) => {
     const isDesktop = useMediaQuery(desktop);
     // const isDesktop = true;
 
-    const CredenzaHeader = isDesktop ? DialogHeader : DrawerHeader;
+    const CredenzaHeader = isDesktop ? DialogHeader : SheetHeader;
 
     return (
         <CredenzaHeader className={className} {...props}>
@@ -124,7 +137,7 @@ const CredenzaTitle = ({ className, children, ...props }: CredenzaProps) => {
     const isDesktop = useMediaQuery(desktop);
     // const isDesktop = true;
 
-    const CredenzaTitle = isDesktop ? DialogTitle : DrawerTitle;
+    const CredenzaTitle = isDesktop ? DialogTitle : SheetTitle;
 
     return (
         <CredenzaTitle className={className} {...props}>
@@ -134,25 +147,24 @@ const CredenzaTitle = ({ className, children, ...props }: CredenzaProps) => {
 };
 
 const CredenzaBody = ({ className, children, ...props }: CredenzaProps) => {
-    return (
-        <div className={cn("px-4 md:px-0 mb-4", className)} {...props}>
-            {children}
-        </div>
-    );
-
-    //     return (
-    //     <div className={cn("px-0 mb-4", className)} {...props}>
+    // return (
+    //     <div className={cn("px-4 md:px-0 mb-4", className)} {...props}>
     //         {children}
     //     </div>
     // );
 
+    return (
+        <div className={cn("px-0 mb-4", className)} {...props}>
+            {children}
+        </div>
+    );
 };
 
 const CredenzaFooter = ({ className, children, ...props }: CredenzaProps) => {
     const isDesktop = useMediaQuery(desktop);
     // const isDesktop = true;
 
-    const CredenzaFooter = isDesktop ? DialogFooter : DrawerFooter;
+    const CredenzaFooter = isDesktop ? DialogFooter : SheetFooter;
 
     return (
         <CredenzaFooter className={className} {...props}>

+ 1 - 1
src/components/Enable2FaForm.tsx

@@ -224,7 +224,7 @@ export default function Enable2FaForm({ open, setOpen }: Enable2FaProps) {
                             <div className="h-[250px] mx-auto flex items-center justify-center">
                                 <QRCodeCanvas value={secretUri} size={200} />
                             </div>
-                            <CopyTextBox text={secretKey} wrapText={false} />
+                            <CopyTextBox text={secretUri} wrapText={false} />
 
                             <Form {...confirmForm}>
                                 <form

+ 140 - 0
src/components/ui/sheet.tsx

@@ -0,0 +1,140 @@
+"use client"
+
+import * as React from "react"
+import * as SheetPrimitive from "@radix-ui/react-dialog"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Sheet = SheetPrimitive.Root
+
+const SheetTrigger = SheetPrimitive.Trigger
+
+const SheetClose = SheetPrimitive.Close
+
+const SheetPortal = SheetPrimitive.Portal
+
+const SheetOverlay = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Overlay
+    className={cn(
+      "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+      className
+    )}
+    {...props}
+    ref={ref}
+  />
+))
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+  "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
+  {
+    variants: {
+      side: {
+        top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
+        bottom:
+          "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
+        left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+        right:
+          "inset-y-0 right-0 h-full w-3/4  border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+      },
+    },
+    defaultVariants: {
+      side: "right",
+    },
+  }
+)
+
+interface SheetContentProps
+  extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
+    VariantProps<typeof sheetVariants> {}
+
+const SheetContent = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Content>,
+  SheetContentProps
+>(({ side = "right", className, children, ...props }, ref) => (
+  <SheetPortal>
+    <SheetOverlay />
+    <SheetPrimitive.Content
+      ref={ref}
+      className={cn(sheetVariants({ side }), className)}
+      {...props}
+    >
+      {children}
+      <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
+        <X className="h-4 w-4" />
+        <span className="sr-only">Close</span>
+      </SheetPrimitive.Close>
+    </SheetPrimitive.Content>
+  </SheetPortal>
+))
+SheetContent.displayName = SheetPrimitive.Content.displayName
+
+const SheetHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col space-y-2 text-center sm:text-left",
+      className
+    )}
+    {...props}
+  />
+)
+SheetHeader.displayName = "SheetHeader"
+
+const SheetFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+      className
+    )}
+    {...props}
+  />
+)
+SheetFooter.displayName = "SheetFooter"
+
+const SheetTitle = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Title
+    ref={ref}
+    className={cn("text-lg font-semibold text-foreground", className)}
+    {...props}
+  />
+))
+SheetTitle.displayName = SheetPrimitive.Title.displayName
+
+const SheetDescription = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Description
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+SheetDescription.displayName = SheetPrimitive.Description.displayName
+
+export {
+  Sheet,
+  SheetPortal,
+  SheetOverlay,
+  SheetTrigger,
+  SheetClose,
+  SheetContent,
+  SheetHeader,
+  SheetFooter,
+  SheetTitle,
+  SheetDescription,
+}

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

@@ -25,7 +25,7 @@ const ToastViewport = React.forwardRef<
 ToastViewport.displayName = ToastPrimitives.Viewport.displayName
 
 const toastVariants = cva(
-  "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+  "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-3 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
   {
     variants: {
       variant: {

+ 1 - 1
src/hooks/useToast.ts

@@ -6,7 +6,7 @@ import * as React from "react";
 import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
 
 const TOAST_LIMIT = 3;
-const TOAST_REMOVE_DELAY = 5 * 1000;
+const TOAST_REMOVE_DELAY = 1 * 1000;
 
 type ToasterToast = ToastProps & {
     id: string;

+ 2 - 2
src/providers/ThemeProvider.tsx

@@ -1,8 +1,8 @@
 "use client";
 
-import * as React from "react";
 import { ThemeProvider as NextThemesProvider } from "next-themes";
-import { type ThemeProviderProps } from "next-themes/dist/types";
+
+type ThemeProviderProps = React.ComponentProps<typeof NextThemesProvider>;
 
 export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
     return <NextThemesProvider {...props}>{children}</NextThemesProvider>;