diff --git a/app/(backend)/users/components/UserForm/index.tsx b/app/(backend)/users/components/UserForm/index.tsx new file mode 100644 index 0000000..d3c8dec --- /dev/null +++ b/app/(backend)/users/components/UserForm/index.tsx @@ -0,0 +1,136 @@ +'use client'; + +import { z } from "zod" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" + +// Shadcn UI components +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from "@/components/ui/select" +import { Switch } from "@/components/ui/switch" + +// 1. Schema definition +const formSchema = z.object({ + email: z.string().email("Invalid email address."), + role: z.string().min(1, "Please select a role."), // Rollenbezeichnung + status: z.boolean().default(true), // Status (Active/Inactive) + password: z.string().min(8, "Password must be at least 8 characters."), +}) + +export type FormValues = z.infer + +export default function UserForm() { + // 2. Initialize the form + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + email: "", + role: "user", + status: true, + password: "", + }, + }) + + // 3. Define the submit handler + function onSubmit(values: FormValues) { + // All comments in code must be in English + // Later we will connect this to a Server Action + console.log("Form values:", values) + } + + return ( +
+ + {/* Email Field */} + ( + + Email + + + + + + )} + /> + + {/* Role Select Field */} + ( + + Role (Rollenbezeichnung) + + + + )} + /> + + {/* Status Switch Field */} + ( + +
+ Active Status +
+ + + +
+ )} + /> + + {/* Password Field */} + ( + + Password + + + + + + )} + /> + + + + + ) +} \ No newline at end of file diff --git a/app/(backend)/users/components/UserFormWrapper.tsx b/app/(backend)/users/components/UserFormWrapper.tsx new file mode 100644 index 0000000..43d5ecb --- /dev/null +++ b/app/(backend)/users/components/UserFormWrapper.tsx @@ -0,0 +1,73 @@ +"use client" + +import * as React from "react" +import { Plus } from "lucide-react" +import { useMediaQuery } from "@/hooks/use-media-query" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer" +import UserForm from "./UserForm" + +export function UserFormWrapper() { + const [open, setOpen] = React.useState(false) + const isDesktop = useMediaQuery("(min-width: 768px)") + + // Common button content + const TriggerButton = ( + + ) + + if (isDesktop) { + return ( + + + {TriggerButton} + + + + Add New User + + + + + ) + } + + return ( + + + {TriggerButton} + + + + Add New User + +
+ +
+
+
+ ) +} \ No newline at end of file diff --git a/app/(backend)/users/page.tsx b/app/(backend)/users/page.tsx index 700d0ea..ed4d1fe 100644 --- a/app/(backend)/users/page.tsx +++ b/app/(backend)/users/page.tsx @@ -2,9 +2,12 @@ import { useState } from "react"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { Search, Plus, MoreVertical, Filter } from "lucide-react"; import { UserMobileCard } from "./components/UserMobileCard"; +import { UserFormWrapper } from "./components/UserFormWrapper"; +// Temporary user data for demonstration const tempUsers = [ { id: 1, name: "John Doe", email: "john@example.com", role: "Admin", joinedAt: "2023-01-01", status: "active" }, { id: 2, name: "Alice Smith", email: "alice@test.com", role: "Editor", joinedAt: "2023-02-15", status: "active" }, @@ -31,23 +34,16 @@ export default function UsersPage() {

- {/* Кнопка: на мобилке круглая, на десктопе — солидная и яркая */} - + {/* Button: Add New User for Desktop only */} + {/* Search + Filter */}
-
@@ -61,13 +57,10 @@ export default function UsersPage() {
- {/* Кнопка: на мобилке круглая, на десктопе — солидная и яркая */} - + {/* Button: Add New User for Mobile only */} +
+ +
diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 0000000..1f0e368 --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,158 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { XIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<"div"> & { + showCloseButton?: boolean +}) { + return ( +
+ {children} + {showCloseButton && ( + + + + )} +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/components/ui/drawer.tsx b/components/ui/drawer.tsx new file mode 100644 index 0000000..8aa6923 --- /dev/null +++ b/components/ui/drawer.tsx @@ -0,0 +1,135 @@ +"use client" + +import * as React from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +function Drawer({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerClose({ + ...props +}: React.ComponentProps) { + return +} + +function DrawerOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DrawerContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + +
+ {children} + + + ) +} + +function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DrawerTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DrawerDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/components/ui/form.tsx b/components/ui/form.tsx new file mode 100644 index 0000000..2b529e6 --- /dev/null +++ b/components/ui/form.tsx @@ -0,0 +1,167 @@ +"use client" + +import * as React from "react" +import type * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + FormProvider, + useFormContext, + useFormState, + type ControllerProps, + type FieldPath, + type FieldValues, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState } = useFormContext() + const formState = useFormState({ name: fieldContext.name }) + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +function FormItem({ className, ...props }: React.ComponentProps<"div">) { + const id = React.useId() + + return ( + +
+ + ) +} + +function FormLabel({ + className, + ...props +}: React.ComponentProps) { + const { error, formItemId } = useFormField() + + return ( +