243 lines
10 KiB
TypeScript
243 lines
10 KiB
TypeScript
"use client"
|
|
|
|
import type React from "react"
|
|
|
|
import { useState } from "react"
|
|
import { useNavigate } from "react-router-dom"
|
|
import { UserPlus, ArrowLeft } from "lucide-react"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { toast } from "react-hot-toast"
|
|
import apiService from "@/services/apiService"
|
|
import Header from "@/components/layout/Header"
|
|
import Sidebar from "@/components/layout/Sidebar"
|
|
import { UserSchema } from "@/schemas/userSchema"
|
|
import { z } from "zod"
|
|
|
|
export default function UserCreate() {
|
|
const navigate = useNavigate()
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [formData, setFormData] = useState({
|
|
name: "",
|
|
email: "",
|
|
password: "",
|
|
password_confirmation: "",
|
|
})
|
|
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({})
|
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const { name, value } = e.target
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
[name]: value,
|
|
}))
|
|
|
|
// Clear error for this field when user types
|
|
if (validationErrors[name]) {
|
|
setValidationErrors(prev => {
|
|
const updated = { ...prev }
|
|
delete updated[name]
|
|
return updated
|
|
})
|
|
}
|
|
}
|
|
|
|
const validateForm = () => {
|
|
try {
|
|
UserSchema.parse(formData)
|
|
setValidationErrors({})
|
|
return true
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
const errors: Record<string, string> = {}
|
|
error.errors.forEach((err) => {
|
|
const path = err.path.join('.')
|
|
errors[path] = err.message
|
|
})
|
|
setValidationErrors(errors)
|
|
return false
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
const getFieldError = (fieldName: string) => {
|
|
return validationErrors[fieldName] || null
|
|
}
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
|
|
// Zod validation
|
|
if (!validateForm()) {
|
|
// Show the first validation error as a toast
|
|
const firstError = Object.values(validationErrors)[0]
|
|
if (firstError) {
|
|
toast.error(firstError)
|
|
}
|
|
return
|
|
}
|
|
|
|
try {
|
|
setIsSubmitting(true)
|
|
|
|
// This would need to be implemented in your apiService
|
|
await apiService.createUser(formData)
|
|
|
|
toast.success("User created successfully!")
|
|
navigate("/admin/settings") // Redirect to an appropriate page
|
|
} catch (error) {
|
|
console.error("Error creating user:", error)
|
|
toast.error("Failed to create user. Please try again.")
|
|
} finally {
|
|
setIsSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-h-dvh bg-gray-950">
|
|
<Sidebar />
|
|
<div className="flex-1 md:ml-16 lg:ml-64 w-full transition-all duration-300">
|
|
<Header title="Create User" />
|
|
<main className="p-4 md:p-6">
|
|
<div className="mb-6">
|
|
<div className="flex items-center gap-2">
|
|
<h1 className="text-2xl md:text-3xl font-serif font-bold text-white">Create New User</h1>
|
|
</div>
|
|
<p className="text-gray-400 mt-2">Add a new administrator to the system</p>
|
|
</div>
|
|
|
|
<div className="max-w-10xl mx-auto">
|
|
<Card className="shadow-2xl border border-gray-800 bg-gray-900 overflow-hidden">
|
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-[#9B2335] to-[#9B2335]/60"></div>
|
|
<CardHeader className="border-b border-gray-800">
|
|
<CardTitle className="text-xl font-serif text-white">User Information</CardTitle>
|
|
<CardDescription className="text-gray-400">Please fill in all required fields</CardDescription>
|
|
</CardHeader>
|
|
<form onSubmit={handleSubmit}>
|
|
<CardContent className="space-y-6 p-6">
|
|
<div className="space-y-6">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="name" className="text-gray-300">
|
|
Full Name <span className="text-red-400">*</span>
|
|
</Label>
|
|
<Input
|
|
id="name"
|
|
name="name"
|
|
placeholder="Enter full name"
|
|
value={formData.name}
|
|
onChange={handleInputChange}
|
|
required
|
|
className={`bg-gray-800 border-gray-700 text-white focus:border-[#9B2335] placeholder:text-gray-500 ${validationErrors.name ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}`}
|
|
/>
|
|
{getFieldError('name') && (
|
|
<p className="text-red-400 text-sm mt-1">{getFieldError('name')}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="email" className="text-gray-300">
|
|
Email Address <span className="text-red-400">*</span>
|
|
</Label>
|
|
<Input
|
|
id="email"
|
|
name="email"
|
|
type="email"
|
|
placeholder="Enter email address"
|
|
value={formData.email}
|
|
onChange={handleInputChange}
|
|
required
|
|
className={`bg-gray-800 border-gray-700 text-white focus:border-[#9B2335] placeholder:text-gray-500 ${validationErrors.email ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}`}
|
|
/>
|
|
{getFieldError('email') && (
|
|
<p className="text-red-400 text-sm mt-1">{getFieldError('email')}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="border-t border-gray-800 pt-6">
|
|
<h3 className="text-lg font-medium mb-4 text-white">Security Information</h3>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="password" className="text-gray-300">
|
|
Password <span className="text-red-400">*</span>
|
|
</Label>
|
|
<Input
|
|
id="password"
|
|
name="password"
|
|
type="password"
|
|
placeholder="Enter password"
|
|
value={formData.password}
|
|
onChange={handleInputChange}
|
|
required
|
|
className={`bg-gray-800 border-gray-700 text-white focus:border-[#9B2335] placeholder:text-gray-500 ${validationErrors.password ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}`}
|
|
/>
|
|
{getFieldError('password') && (
|
|
<p className="text-red-400 text-sm mt-1">{getFieldError('password')}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="password_confirmation" className="text-gray-300">
|
|
Confirm Password <span className="text-red-400">*</span>
|
|
</Label>
|
|
<Input
|
|
id="password_confirmation"
|
|
name="password_confirmation"
|
|
type="password"
|
|
placeholder="Confirm password"
|
|
value={formData.password_confirmation}
|
|
onChange={handleInputChange}
|
|
required
|
|
className={`bg-gray-800 border-gray-700 text-white focus:border-[#9B2335] placeholder:text-gray-500 ${validationErrors.password_confirmation ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}`}
|
|
/>
|
|
{getFieldError('password_confirmation') && (
|
|
<p className="text-red-400 text-sm mt-1">{getFieldError('password_confirmation')}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Display validation errors summary if needed */}
|
|
{Object.keys(validationErrors).length > 0 && (
|
|
<div className="mt-6 p-4 bg-red-900/20 border border-red-500/30 rounded-lg">
|
|
<h4 className="text-red-400 font-medium mb-2">Please fix the following errors:</h4>
|
|
<ul className="text-sm text-red-300 space-y-1">
|
|
{Object.entries(validationErrors).map(([field, error]) => (
|
|
<li key={field}>• {error}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
|
|
<CardFooter className="flex justify-end gap-3 pt-2 pb-6 px-6 border-t border-gray-800">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => navigate("/admin/settings")}
|
|
disabled={isSubmitting}
|
|
className="border-gray-700 text-gray-300 hover:bg-gray-800 hover:text-white bg-gray-900 shadow-lg"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
disabled={isSubmitting}
|
|
className="bg-[#9B2335] hover:bg-[#9B2335]/90 text-white shadow-lg"
|
|
>
|
|
<UserPlus className="mr-2 size-4" />
|
|
{isSubmitting ? "Creating..." : "Create User"}
|
|
</Button>
|
|
</CardFooter>
|
|
</form>
|
|
</Card>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|