483 lines
16 KiB
TypeScript
483 lines
16 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { z } from "zod";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { format } from "date-fns";
|
|
import { CalendarIcon, X } from "lucide-react";
|
|
|
|
import {
|
|
Sheet,
|
|
SheetContent,
|
|
SheetDescription,
|
|
SheetHeader,
|
|
SheetTitle,
|
|
SheetFooter,
|
|
SheetClose,
|
|
} from "@/app/_components/ui/sheet";
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormDescription,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from "@/app/_components/ui/form";
|
|
import { Input } from "@/app/_components/ui/input";
|
|
import { Button } from "@/app/_components/ui/button";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/app/_components/ui/select";
|
|
import { Textarea } from "@/app/_components/ui/textarea";
|
|
import {
|
|
Avatar,
|
|
AvatarFallback,
|
|
AvatarImage,
|
|
} from "@/app/_components/ui/avatar";
|
|
import { Calendar } from "@/app/_components/ui/calendar";
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from "@/app/_components/ui/popover";
|
|
import { Separator } from "@/app/_components/ui/separator";
|
|
import {
|
|
Tabs,
|
|
TabsContent,
|
|
TabsList,
|
|
TabsTrigger,
|
|
} from "@/app/_components/ui/tabs";
|
|
import { User } from "./users-table";
|
|
|
|
// Create a schema for form validation
|
|
const userFormSchema = z.object({
|
|
// User fields
|
|
email: z.string().email({ message: "Please enter a valid email address" }),
|
|
firstName: z.string().min(1, { message: "First name is required" }),
|
|
lastName: z.string().min(1, { message: "Last name is required" }),
|
|
avatar: z.string().optional(),
|
|
role: z.enum(["admin", "staff", "user"]),
|
|
password: z
|
|
.string()
|
|
.min(8, { message: "Password must be at least 8 characters long" })
|
|
.optional(),
|
|
|
|
// Profile fields
|
|
bio: z.string().optional(),
|
|
phone: z.string().optional(),
|
|
address: z.string().optional(),
|
|
city: z.string().optional(),
|
|
country: z.string().optional(),
|
|
birthDate: z.date().optional(),
|
|
});
|
|
|
|
type UserFormValues = z.infer<typeof userFormSchema>;
|
|
|
|
// Extended user type to include profile information
|
|
type UserWithProfile = User & {
|
|
profile?: {
|
|
bio?: string;
|
|
phone?: string;
|
|
address?: string;
|
|
city?: string;
|
|
country?: string;
|
|
birthDate?: Date;
|
|
};
|
|
};
|
|
|
|
interface EditUserSheetProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
user?: UserWithProfile | null;
|
|
onSave: (user: UserWithProfile) => void;
|
|
isNew?: boolean;
|
|
}
|
|
|
|
export function EditUserSheet({
|
|
open,
|
|
onOpenChange,
|
|
user,
|
|
onSave,
|
|
isNew = false,
|
|
}: EditUserSheetProps) {
|
|
const form = useForm<UserFormValues>({
|
|
resolver: zodResolver(userFormSchema),
|
|
defaultValues: {
|
|
email: "",
|
|
firstName: "",
|
|
lastName: "",
|
|
avatar: "/placeholder.svg?height=40&width=40",
|
|
role: "user",
|
|
password: isNew ? "" : undefined,
|
|
bio: "",
|
|
phone: "",
|
|
address: "",
|
|
city: "",
|
|
country: "",
|
|
birthDate: undefined,
|
|
},
|
|
});
|
|
|
|
// Update form when user changes
|
|
useEffect(() => {
|
|
if (user) {
|
|
const birthDate = user.profile?.birthDate
|
|
? new Date(user.profile.birthDate)
|
|
: undefined;
|
|
|
|
form.reset({
|
|
email: user.email,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
avatar: user.avatar,
|
|
role: user.role,
|
|
// Don't populate password for existing users
|
|
bio: user.profile?.bio || "",
|
|
phone: user.profile?.phone || "",
|
|
address: user.profile?.address || "",
|
|
city: user.profile?.city || "",
|
|
country: user.profile?.country || "",
|
|
birthDate: birthDate,
|
|
});
|
|
} else if (isNew) {
|
|
form.reset({
|
|
email: "",
|
|
firstName: "",
|
|
lastName: "",
|
|
avatar: "/placeholder.svg?height=40&width=40",
|
|
role: "user",
|
|
password: "",
|
|
bio: "",
|
|
phone: "",
|
|
address: "",
|
|
city: "",
|
|
country: "",
|
|
birthDate: undefined,
|
|
});
|
|
}
|
|
}, [user, isNew, form]);
|
|
|
|
const onSubmit = (data: UserFormValues) => {
|
|
// Prepare the user object with profile
|
|
const updatedUser: UserWithProfile = {
|
|
id: user?.id || "new-id", // In a real app, this would be handled by the backend
|
|
email: data.email,
|
|
firstName: data.firstName || "",
|
|
lastName: data.lastName || "",
|
|
avatar: data.avatar || "/placeholder.svg?height=40&width=40",
|
|
role: data.role,
|
|
status: user?.status || "active",
|
|
lastSignedIn:
|
|
user?.lastSignedIn || new Date().toISOString().split("T")[0],
|
|
profile: {
|
|
bio: data.bio,
|
|
phone: data.phone,
|
|
address: data.address,
|
|
city: data.city,
|
|
country: data.country,
|
|
birthDate: data.birthDate,
|
|
},
|
|
};
|
|
|
|
// Save the user
|
|
onSave(updatedUser);
|
|
|
|
// Close the sheet
|
|
onOpenChange(false);
|
|
};
|
|
|
|
return (
|
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
<SheetContent className="sm:max-w-xl overflow-y-auto">
|
|
<SheetHeader>
|
|
<SheetTitle>{isNew ? "Add New User" : "Edit User"}</SheetTitle>
|
|
<SheetDescription>
|
|
{isNew
|
|
? "Fill in the details to create a new user account."
|
|
: "Make changes to the user profile here."}
|
|
</SheetDescription>
|
|
</SheetHeader>
|
|
<div className="py-4">
|
|
<Tabs defaultValue="user" className="w-full">
|
|
<TabsList className="grid w-full grid-cols-2">
|
|
<TabsTrigger value="user">User Details</TabsTrigger>
|
|
<TabsTrigger value="profile">Profile Information</TabsTrigger>
|
|
</TabsList>
|
|
<Form {...form}>
|
|
<form
|
|
onSubmit={form.handleSubmit(onSubmit)}
|
|
className="space-y-6 pt-6"
|
|
>
|
|
<TabsContent value="user" className="space-y-4">
|
|
<div className="flex justify-center mb-6">
|
|
<Avatar className="h-24 w-24">
|
|
<AvatarImage
|
|
src={
|
|
form.watch("avatar") ||
|
|
"/placeholder.svg?height=96&width=96"
|
|
}
|
|
alt="User avatar"
|
|
/>
|
|
<AvatarFallback>
|
|
{form.watch("firstName")?.charAt(0) || ""}
|
|
{form.watch("lastName")?.charAt(0) || ""}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="firstName"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>First Name</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="First name" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={form.control}
|
|
name="lastName"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Last Name</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Last name" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="email"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Email</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
placeholder="Email address"
|
|
type="email"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
{isNew && (
|
|
<FormField
|
|
control={form.control}
|
|
name="password"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Password</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
placeholder="Set password"
|
|
type="password"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormDescription>
|
|
Minimum 8 characters
|
|
</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
)}
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="role"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Role</FormLabel>
|
|
<Select
|
|
onValueChange={field.onChange}
|
|
defaultValue={field.value}
|
|
value={field.value}
|
|
>
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select a role" />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="admin">Admin</SelectItem>
|
|
<SelectItem value="staff">Staff</SelectItem>
|
|
<SelectItem value="user">User</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="avatar"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Avatar URL</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Avatar URL" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="profile" className="space-y-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="bio"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Bio</FormLabel>
|
|
<FormControl>
|
|
<Textarea
|
|
placeholder="Tell us a little about this user"
|
|
className="resize-none"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="phone"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Phone</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Phone number" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="address"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Address</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Street address" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="city"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>City</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="City" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={form.control}
|
|
name="country"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Country</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="Country" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="birthDate"
|
|
render={({ field }) => (
|
|
<FormItem className="flex flex-col">
|
|
<FormLabel>Date of Birth</FormLabel>
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<FormControl>
|
|
<Button
|
|
variant={"outline"}
|
|
className={`w-full pl-3 text-left font-normal ${
|
|
!field.value && "text-muted-foreground"
|
|
}`}
|
|
>
|
|
{field.value ? (
|
|
format(field.value, "PPP")
|
|
) : (
|
|
<span>Pick a date</span>
|
|
)}
|
|
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
|
</Button>
|
|
</FormControl>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-auto p-0" align="start">
|
|
<Calendar
|
|
mode="single"
|
|
selected={field.value}
|
|
onSelect={field.onChange}
|
|
disabled={(date: Date) =>
|
|
date > new Date() ||
|
|
date < new Date("1900-01-01")
|
|
}
|
|
initialFocus
|
|
/>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<SheetFooter className="pt-4">
|
|
<SheetClose asChild>
|
|
<Button variant="outline" type="button">
|
|
Cancel
|
|
</Button>
|
|
</SheetClose>
|
|
<Button type="submit">
|
|
{isNew ? "Create User" : "Save Changes"}
|
|
</Button>
|
|
</SheetFooter>
|
|
</form>
|
|
</Form>
|
|
</Tabs>
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
);
|
|
}
|