Files
membership-fe/src/components/CreateStaffDialog.js
2025-12-16 20:04:00 +07:00

255 lines
8.2 KiB
JavaScript

import React, { useState } from 'react';
import api from '../utils/api';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from './ui/dialog';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Label } from './ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { toast } from 'sonner';
import { Loader2, UserPlus } from 'lucide-react';
const CreateStaffDialog = ({ open, onOpenChange, onSuccess }) => {
const [formData, setFormData] = useState({
email: '',
password: '',
first_name: '',
last_name: '',
phone: '',
role: 'admin'
});
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState({});
const handleChange = (field, value) => {
setFormData(prev => ({ ...prev, [field]: value }));
// Clear error when user starts typing
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: null }));
}
};
const validate = () => {
const newErrors = {};
if (!formData.email) {
newErrors.email = 'Email is required';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Invalid email format';
}
if (!formData.password || formData.password.length < 8) {
newErrors.password = 'Password must be at least 8 characters';
}
if (!formData.first_name) {
newErrors.first_name = 'First name is required';
}
if (!formData.last_name) {
newErrors.last_name = 'Last name is required';
}
if (!formData.phone) {
newErrors.phone = 'Phone is required';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!validate()) {
return;
}
setLoading(true);
try {
await api.post('/admin/users/create', formData);
toast.success('Staff member created successfully');
// Reset form
setFormData({
email: '',
password: '',
first_name: '',
last_name: '',
phone: '',
role: 'admin'
});
onOpenChange(false);
if (onSuccess) onSuccess();
} catch (error) {
const errorMessage = error.response?.data?.detail || 'Failed to create staff member';
toast.error(errorMessage);
} finally {
setLoading(false);
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[600px] rounded-2xl">
<DialogHeader>
<DialogTitle className="text-2xl text-[#422268] flex items-center gap-2" style={{ fontFamily: "'Inter', sans-serif" }}>
<UserPlus className="h-6 w-6" />
Create Staff Member
</DialogTitle>
<DialogDescription className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Create a new staff account with direct login access. User will be created immediately.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit}>
<div className="grid gap-6 py-4">
{/* Email */}
<div className="grid gap-2">
<Label htmlFor="email" className="text-[#422268]">
Email <span className="text-red-500">*</span>
</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleChange('email', e.target.value)}
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
placeholder="staff@example.com"
/>
{errors.email && (
<p className="text-sm text-red-500">{errors.email}</p>
)}
</div>
{/* Password */}
<div className="grid gap-2">
<Label htmlFor="password" className="text-[#422268]">
Password <span className="text-red-500">*</span>
</Label>
<Input
id="password"
type="password"
value={formData.password}
onChange={(e) => handleChange('password', e.target.value)}
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
placeholder="Minimum 8 characters"
/>
{errors.password && (
<p className="text-sm text-red-500">{errors.password}</p>
)}
</div>
{/* First Name */}
<div className="grid gap-2">
<Label htmlFor="first_name" className="text-[#422268]">
First Name <span className="text-red-500">*</span>
</Label>
<Input
id="first_name"
value={formData.first_name}
onChange={(e) => handleChange('first_name', e.target.value)}
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
placeholder="John"
/>
{errors.first_name && (
<p className="text-sm text-red-500">{errors.first_name}</p>
)}
</div>
{/* Last Name */}
<div className="grid gap-2">
<Label htmlFor="last_name" className="text-[#422268]">
Last Name <span className="text-red-500">*</span>
</Label>
<Input
id="last_name"
value={formData.last_name}
onChange={(e) => handleChange('last_name', e.target.value)}
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
placeholder="Doe"
/>
{errors.last_name && (
<p className="text-sm text-red-500">{errors.last_name}</p>
)}
</div>
{/* Phone */}
<div className="grid gap-2">
<Label htmlFor="phone" className="text-[#422268]">
Phone <span className="text-red-500">*</span>
</Label>
<Input
id="phone"
type="tel"
value={formData.phone}
onChange={(e) => handleChange('phone', e.target.value)}
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
placeholder="(555) 123-4567"
/>
{errors.phone && (
<p className="text-sm text-red-500">{errors.phone}</p>
)}
</div>
{/* Role */}
<div className="grid gap-2">
<Label htmlFor="role" className="text-[#422268]">
Role <span className="text-red-500">*</span>
</Label>
<Select value={formData.role} onValueChange={(value) => handleChange('role', value)}>
<SelectTrigger className="rounded-xl border-2 border-[#ddd8eb]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="superadmin">Superadmin</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
className="rounded-xl"
disabled={loading}
>
Cancel
</Button>
<Button
type="submit"
className="rounded-xl bg-[#81B29A] hover:bg-[#6DA085] text-white"
disabled={loading}
>
{loading ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Creating...
</>
) : (
<>
<UserPlus className="h-4 w-4 mr-2" />
Create Staff
</>
)}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
};
export default CreateStaffDialog;