280 lines
7.8 KiB
TypeScript
280 lines
7.8 KiB
TypeScript
"use client";
|
||
|
||
import React, { useState } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
import "./AddUser.scss";
|
||
import { addUser } from "@/services/roles.services";
|
||
import { IEditUserForm } from "../User.interfaces";
|
||
import { COUNTRY_CODES } from "../constants";
|
||
import { formatPhoneDisplay, validatePhone } from "../utils";
|
||
|
||
interface AddUserFormProps {
|
||
onSuccess?: () => void;
|
||
}
|
||
|
||
const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
||
const router = useRouter();
|
||
const [form, setForm] = useState<IEditUserForm>({
|
||
username: "",
|
||
firstName: "",
|
||
lastName: "",
|
||
email: "",
|
||
phone: "",
|
||
role: "",
|
||
merchants: [],
|
||
groups: [],
|
||
jobTitle: "",
|
||
});
|
||
|
||
const [error, setError] = useState("");
|
||
const [loading, setLoading] = useState(false);
|
||
const [phoneError, setPhoneError] = useState("");
|
||
const [countryCode, setCountryCode] = useState("+1");
|
||
|
||
const handleChange = (
|
||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||
) => {
|
||
const { name, value } = e.target;
|
||
|
||
if (name === "countryCode") {
|
||
setCountryCode(value);
|
||
return;
|
||
}
|
||
|
||
// Handle array fields (merchants and groups)
|
||
if (name === "merchants" || name === "groups") {
|
||
if (value === "") {
|
||
// If empty selection, set empty array
|
||
setForm(prev => ({ ...prev, [name]: [] }));
|
||
} else {
|
||
// Add the selected value to the array if not already present
|
||
setForm(prev => {
|
||
const currentArray = prev[name as keyof IEditUserForm] as string[];
|
||
if (!currentArray.includes(value)) {
|
||
return { ...prev, [name]: [...currentArray, value] };
|
||
}
|
||
return prev;
|
||
});
|
||
}
|
||
} else {
|
||
// Handle single value fields
|
||
setForm(prev => ({ ...prev, [name]: value }));
|
||
}
|
||
};
|
||
|
||
const handleCountryCodeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||
const newCountryCode = e.target.value;
|
||
setCountryCode(newCountryCode);
|
||
|
||
// Re-validate phone if it exists
|
||
if (form.phone) {
|
||
const phoneError = validatePhone(form.phone, newCountryCode);
|
||
setPhoneError(phoneError);
|
||
}
|
||
};
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
|
||
// Validate phone number if provided
|
||
if (form.phone && phoneError) {
|
||
setError("Please fix phone number errors before submitting.");
|
||
return;
|
||
}
|
||
|
||
if (
|
||
!form.firstName ||
|
||
!form.lastName ||
|
||
!form.email ||
|
||
form.merchants.length === 0 ||
|
||
form.groups.length === 0 ||
|
||
!form.jobTitle
|
||
) {
|
||
setError("Please fill in all required fields.");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
setError("");
|
||
|
||
// Format phone number with country code before submission
|
||
const formattedForm = {
|
||
...form,
|
||
phone: form.phone ? formatPhoneDisplay(form.phone, countryCode) : "",
|
||
};
|
||
|
||
await addUser(formattedForm);
|
||
if (onSuccess) onSuccess();
|
||
router.refresh(); // <- refreshes the page (SSR re-runs)
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||
} catch (err: any) {
|
||
setError(err.message || "Something went wrong.");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<form className="add-user" onSubmit={handleSubmit}>
|
||
<input
|
||
name="username"
|
||
placeholder="Username"
|
||
value={form.username}
|
||
onChange={handleChange}
|
||
required
|
||
/>
|
||
<input
|
||
name="firstName"
|
||
placeholder="First Name"
|
||
value={form.firstName}
|
||
onChange={handleChange}
|
||
required
|
||
/>
|
||
<input
|
||
name="lastName"
|
||
placeholder="Last Name"
|
||
value={form.lastName}
|
||
onChange={handleChange}
|
||
required
|
||
/>
|
||
<input
|
||
name="email"
|
||
type="email"
|
||
placeholder="Email"
|
||
value={form.email}
|
||
onChange={handleChange}
|
||
required
|
||
/>
|
||
<div className="array-field-container">
|
||
<label>Merchants:</label>
|
||
<select
|
||
name="merchants"
|
||
value=""
|
||
onChange={handleChange}
|
||
className="add-user__select"
|
||
>
|
||
<option value="">Select Merchant</option>
|
||
<option value="Win Bot">Win Bot</option>
|
||
<option value="Data Spin">Data Spin</option>
|
||
</select>
|
||
{form.merchants.length > 0 && (
|
||
<div className="selected-items">
|
||
{form.merchants.map((merchant, index) => (
|
||
<span key={index} className="selected-item">
|
||
{merchant}
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
setForm(prev => ({
|
||
...prev,
|
||
merchants: prev.merchants.filter((_, i) => i !== index),
|
||
}));
|
||
}}
|
||
className="remove-item"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="array-field-container">
|
||
<label>Groups:</label>
|
||
<select
|
||
name="groups"
|
||
value=""
|
||
onChange={handleChange}
|
||
className="add-user__select"
|
||
>
|
||
<option value="">Select Group</option>
|
||
<option value="Admin">Admin</option>
|
||
<option value="Reader">Reader</option>
|
||
<option value="Manager">Manager</option>
|
||
<option value="User">User</option>
|
||
</select>
|
||
{form.groups.length > 0 && (
|
||
<div className="selected-items">
|
||
{form.groups.map((group, index) => (
|
||
<span key={index} className="selected-item">
|
||
{group}
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
setForm(prev => ({
|
||
...prev,
|
||
groups: prev.groups.filter((_, i) => i !== index),
|
||
}));
|
||
}}
|
||
className="remove-item"
|
||
>
|
||
×
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
<select
|
||
name="jobTitle"
|
||
value={form.jobTitle}
|
||
onChange={handleChange}
|
||
required
|
||
className="add-user__select"
|
||
>
|
||
<option value="">Select Job Title</option>
|
||
<option value="Admin">Admin</option>
|
||
<option value="Reader">Reader</option>
|
||
<option value="User">User</option>
|
||
<option value="Manager">Manager</option>
|
||
<option value="Supervisor">Supervisor</option>
|
||
<option value="Director">Director</option>
|
||
</select>
|
||
<div className="phone-input-container">
|
||
<select
|
||
name="countryCode"
|
||
value={countryCode}
|
||
onChange={handleCountryCodeChange}
|
||
className="country-code-select"
|
||
>
|
||
{COUNTRY_CODES.map(country => (
|
||
<option key={country.code} value={country.code}>
|
||
{country.flag} {country.code} {country.country}
|
||
</option>
|
||
))}
|
||
</select>
|
||
<input
|
||
name="phone"
|
||
type="tel"
|
||
placeholder="Phone number (optional)"
|
||
value={form.phone}
|
||
onChange={handleChange}
|
||
className={phoneError ? "phone-input-error" : ""}
|
||
/>
|
||
</div>
|
||
|
||
{error && <span style={{ color: "red", width: "100%" }}>{error}</span>}
|
||
<span
|
||
className="phone-error-message"
|
||
style={{
|
||
visibility: phoneError ? "visible" : "hidden",
|
||
color: "red",
|
||
width: "100%",
|
||
}}
|
||
>
|
||
{phoneError}
|
||
</span>
|
||
|
||
<div className="add-user__button-container">
|
||
<button type="submit" disabled={loading}>
|
||
{loading ? "Adding..." : "Add User"}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
);
|
||
};
|
||
|
||
export default AddUserForm;
|