diff --git a/package.json b/package.json index 68ad314a..8d6621e4 100644 --- a/package.json +++ b/package.json @@ -62,18 +62,15 @@ "rebuild": "0.1.2", "tailwind-merge": "2.5.3", "tailwindcss-animate": "1.0.7", + "vaul": "1.1.1", "winston": "3.14.2", "winston-daily-rotate-file": "5.0.0", "zod": "3.23.8", "zod-validation-error": "3.4.0" }, "devDependencies": { - "drizzle-kit": "0.24.2", - "esbuild": "0.20.1", - "esbuild-node-externals": "1.13.0", - "yargs": "17.7.2", - "@esbuild-plugins/tsconfig-paths": "0.1.2", "@dotenvx/dotenvx": "1.14.2", + "@esbuild-plugins/tsconfig-paths": "0.1.2", "@types/better-sqlite3": "7.6.11", "@types/cookie-parser": "1.4.7", "@types/cors": "2.8.17", @@ -84,6 +81,9 @@ "@types/react": "npm:types-react@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "@types/yargs": "17.0.33", + "drizzle-kit": "0.24.2", + "esbuild": "0.20.1", + "esbuild-node-externals": "1.13.0", "eslint": "^8", "eslint-config-next": "15.0.1", "postcss": "^8", @@ -91,7 +91,8 @@ "tailwindcss": "^3.4.1", "tsc-alias": "1.8.10", "tsx": "4.19.1", - "typescript": "^5" + "typescript": "^5", + "yargs": "17.7.2" }, "overrides": { "@types/react": "npm:types-react@19.0.0-rc.1", diff --git a/src/app/[orgId]/settings/users/components/InviteUserForm.tsx b/src/app/[orgId]/settings/users/components/InviteUserForm.tsx index 1823f91b..c693eb7f 100644 --- a/src/app/[orgId]/settings/users/components/InviteUserForm.tsx +++ b/src/app/[orgId]/settings/users/components/InviteUserForm.tsx @@ -64,7 +64,7 @@ export default function InviteUserForm() { resolver: zodResolver(formSchema), defaultValues: { email: "", - validForHours: "24", + validForHours: "168", roleId: "4", }, }); @@ -189,15 +189,14 @@ export default function InviteUserForm() { )} /> -
- -
+ )} @@ -216,7 +215,7 @@ export default function InviteUserForm() { .

- {/* */} + )} diff --git a/src/app/[orgId]/settings/users/components/UsersTable.tsx b/src/app/[orgId]/settings/users/components/UsersTable.tsx index 2922249f..d8f730d7 100644 --- a/src/app/[orgId]/settings/users/components/UsersTable.tsx +++ b/src/app/[orgId]/settings/users/components/UsersTable.tsx @@ -19,6 +19,7 @@ import { } from "@app/components/ui/dialog"; import { useState } from "react"; import InviteUserForm from "./InviteUserForm"; +import { Credenza, CredenzaTitle, CredenzaDescription, CredenzaHeader, CredenzaClose, CredenzaFooter, CredenzaContent, CredenzaBody } from "@app/components/Credenza"; export type UserRow = { id: string; @@ -73,17 +74,19 @@ export default function UsersTable({ users }: UsersTableProps) { return ( <> - - - - Invite User - + + + + Invite User + + Give new users access to your organization + + + - - + + + (null); + const [loading, setLoading] = useState(false); const form = useForm>({ resolver: zodResolver(formSchema), @@ -53,6 +54,9 @@ export default function LoginForm({ redirect }: LoginFormProps) { async function onSubmit(values: z.infer) { const { email, password } = values; + + setLoading(true); + const res = await api .post>("/auth/login", { email, @@ -86,6 +90,8 @@ export default function LoginForm({ redirect }: LoginFormProps) { router.push("/"); } } + + setLoading(false); } return ( @@ -140,7 +146,7 @@ export default function LoginForm({ redirect }: LoginFormProps) { {error} )} - diff --git a/src/app/auth/signup/SignupForm.tsx b/src/app/auth/signup/SignupForm.tsx index 2991183a..b8af07aa 100644 --- a/src/app/auth/signup/SignupForm.tsx +++ b/src/app/auth/signup/SignupForm.tsx @@ -46,6 +46,7 @@ const formSchema = z export default function SignupForm({ redirect }: SignupFormProps) { const router = useRouter(); + const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const form = useForm>({ @@ -59,6 +60,8 @@ export default function SignupForm({ redirect }: SignupFormProps) { async function onSubmit(values: z.infer) { const { email, password } = values; + + setLoading(true); const res = await api .put>("/auth/signup", { email, @@ -92,6 +95,8 @@ export default function SignupForm({ redirect }: SignupFormProps) { router.push("/"); } } + + setLoading(false); } return ( diff --git a/src/components/Credenza.tsx b/src/components/Credenza.tsx new file mode 100644 index 00000000..cfadd9b6 --- /dev/null +++ b/src/components/Credenza.tsx @@ -0,0 +1,152 @@ +"use client"; + +import * as React from "react"; + +import { cn } from "@/lib/utils"; +import { useMediaQuery } from "@app/hooks/useMediaQuery"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer"; + +interface BaseProps { + children: React.ReactNode; +} + +interface RootCredenzaProps extends BaseProps { + open?: boolean; + onOpenChange?: (open: boolean) => void; +} + +interface CredenzaProps extends BaseProps { + className?: string; + asChild?: true; +} + +const desktop = "(min-width: 768px)"; + +const Credenza = ({ children, ...props }: RootCredenzaProps) => { + const isDesktop = useMediaQuery(desktop); + const Credenza = isDesktop ? Dialog : Drawer; + + return {children}; +}; + +const CredenzaTrigger = ({ className, children, ...props }: CredenzaProps) => { + const isDesktop = useMediaQuery(desktop); + const CredenzaTrigger = isDesktop ? DialogTrigger : DrawerTrigger; + + return ( + + {children} + + ); +}; + +const CredenzaClose = ({ className, children, ...props }: CredenzaProps) => { + const isDesktop = useMediaQuery(desktop); + const CredenzaClose = isDesktop ? DialogClose : DrawerClose; + + return ( + + {children} + + ); +}; + +const CredenzaContent = ({ className, children, ...props }: CredenzaProps) => { + const isDesktop = useMediaQuery(desktop); + const CredenzaContent = isDesktop ? DialogContent : DrawerContent; + + return ( + + {children} + + ); +}; + +const CredenzaDescription = ({ + className, + children, + ...props +}: CredenzaProps) => { + const isDesktop = useMediaQuery(desktop); + const CredenzaDescription = isDesktop + ? DialogDescription + : DrawerDescription; + + return ( + + {children} + + ); +}; + +const CredenzaHeader = ({ className, children, ...props }: CredenzaProps) => { + const isDesktop = useMediaQuery(desktop); + const CredenzaHeader = isDesktop ? DialogHeader : DrawerHeader; + + return ( + + {children} + + ); +}; + +const CredenzaTitle = ({ className, children, ...props }: CredenzaProps) => { + const isDesktop = useMediaQuery(desktop); + const CredenzaTitle = isDesktop ? DialogTitle : DrawerTitle; + + return ( + + {children} + + ); +}; + +const CredenzaBody = ({ className, children, ...props }: CredenzaProps) => { + return ( +
+ {children} +
+ ); +}; + +const CredenzaFooter = ({ className, children, ...props }: CredenzaProps) => { + const isDesktop = useMediaQuery(desktop); + const CredenzaFooter = isDesktop ? DialogFooter : DrawerFooter; + + return ( + + {children} + + ); +}; + +export { + Credenza, + CredenzaTrigger, + CredenzaClose, + CredenzaContent, + CredenzaDescription, + CredenzaHeader, + CredenzaTitle, + CredenzaBody, + CredenzaFooter, +}; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index 7d24311b..52e291b6 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -38,7 +38,7 @@ const DialogContent = React.forwardRef< ) => ( + +) +Drawer.displayName = "Drawer" + +const DrawerTrigger = DrawerPrimitive.Trigger + +const DrawerPortal = DrawerPrimitive.Portal + +const DrawerClose = DrawerPrimitive.Close + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)) +DrawerContent.displayName = "DrawerContent" + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerHeader.displayName = "DrawerHeader" + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerFooter.displayName = "DrawerFooter" + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerTitle.displayName = DrawerPrimitive.Title.displayName + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerDescription.displayName = DrawerPrimitive.Description.displayName + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts new file mode 100644 index 00000000..6436177b --- /dev/null +++ b/src/hooks/useMediaQuery.ts @@ -0,0 +1,19 @@ +import * as React from "react"; + +export function useMediaQuery(query: string) { + const [value, setValue] = React.useState(false); + + React.useEffect(() => { + function onChange(event: MediaQueryListEvent) { + setValue(event.matches); + } + + const result = matchMedia(query); + result.addEventListener("change", onChange); + setValue(result.matches); + + return () => result.removeEventListener("change", onChange); + }, [query]); + + return value; +}