312 lines
8.8 KiB
TypeScript
312 lines
8.8 KiB
TypeScript
"use client";
|
||
|
||
import React, { useState } from "react";
|
||
import { RootState } from "@/app/redux/store";
|
||
import toast from "react-hot-toast";
|
||
import { useDispatch, useSelector } from "react-redux";
|
||
import { AppDispatch } from "@/app/redux/store";
|
||
import { addUser } from "@/app/redux/auth/authSlice";
|
||
import { IEditUserForm } from "../User.interfaces";
|
||
import { formatPhoneDisplay, validatePhone } from "../utils";
|
||
import Spinner from "../../../components/Spinner/Spinner";
|
||
import Modal from "@/app/components/Modal/Modal";
|
||
import {
|
||
selectAppMetadata,
|
||
selectPhoneNumberCountries,
|
||
} from "@/app/redux/metadata/selectors";
|
||
import "./AddUser.scss";
|
||
|
||
interface AddUserProps {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
}
|
||
|
||
const AddUser: React.FC<AddUserProps> = ({ open, onClose }) => {
|
||
const dispatch = useDispatch<AppDispatch>();
|
||
const { status, error: authError } = useSelector(
|
||
(state: RootState) => state.auth
|
||
);
|
||
const [form, setForm] = useState<IEditUserForm>({
|
||
username: "",
|
||
first_name: "",
|
||
last_name: "",
|
||
email: "",
|
||
phone: "",
|
||
merchants: [],
|
||
groups: [],
|
||
job_title: "",
|
||
});
|
||
|
||
const [phoneError, setPhoneError] = useState("");
|
||
const [countryCode, setCountryCode] = useState("+1");
|
||
const data = useSelector(selectAppMetadata);
|
||
const { merchants = [], groups = [], job_titles = [] } = data || {};
|
||
|
||
const loading = status === "loading";
|
||
|
||
const COUNTRY_CODES = useSelector(selectPhoneNumberCountries);
|
||
|
||
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) {
|
||
return;
|
||
}
|
||
|
||
if (
|
||
!form.first_name ||
|
||
!form.last_name ||
|
||
!form.email ||
|
||
form.merchants.length === 0 ||
|
||
form.groups.length === 0 ||
|
||
!form.job_title
|
||
) {
|
||
return;
|
||
}
|
||
|
||
// Format phone number with country code before submission
|
||
const formattedForm = {
|
||
...form,
|
||
phone: form.phone ? formatPhoneDisplay(form.phone, countryCode) : "",
|
||
};
|
||
|
||
try {
|
||
const resultAction = await dispatch(addUser(formattedForm));
|
||
const result = addUser.fulfilled.match(resultAction);
|
||
|
||
if (result && resultAction.payload.success) {
|
||
toast.success(resultAction.payload.message);
|
||
onClose();
|
||
}
|
||
} catch (err) {
|
||
// Error is handled by Redux state
|
||
console.error("Failed to add user:", err);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Modal open={open} onClose={onClose} title="Add User">
|
||
<form className="add-user" onSubmit={handleSubmit}>
|
||
<input
|
||
name="username"
|
||
placeholder="Username"
|
||
value={form.username}
|
||
onChange={handleChange}
|
||
required
|
||
/>
|
||
<input
|
||
name="first_name"
|
||
placeholder="First Name"
|
||
value={form.first_name}
|
||
onChange={handleChange}
|
||
required
|
||
/>
|
||
<input
|
||
name="last_name"
|
||
placeholder="Last Name"
|
||
value={form.last_name}
|
||
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>
|
||
{merchants.map((merchant: string) => (
|
||
<option key={merchant} value={merchant}>
|
||
{merchant}
|
||
</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>
|
||
{groups.map((group: string) => (
|
||
<option key={group} value={group}>
|
||
{group}
|
||
</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>
|
||
<div className="array-field-container">
|
||
<label>Job Title:</label>
|
||
<select
|
||
name="job_title"
|
||
value={form.job_title}
|
||
onChange={handleChange}
|
||
required
|
||
className="add-user__select"
|
||
>
|
||
<option value="">Select Job Title</option>
|
||
{job_titles?.map((job_title: string) => (
|
||
<option key={job_title} value={job_title}>
|
||
{job_title}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
<div className="phone-input-container">
|
||
<select
|
||
name="countryCode"
|
||
value={countryCode}
|
||
onChange={handleCountryCodeChange}
|
||
className="country-code-select"
|
||
>
|
||
{COUNTRY_CODES.map((country, i) => (
|
||
<option
|
||
key={`${country.code}-${country.name} ${i}`}
|
||
value={country.code}
|
||
>
|
||
{country.flag} {country.code} {country.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
<input
|
||
name="phone"
|
||
type="tel"
|
||
placeholder="Phone number (optional)"
|
||
value={form.phone}
|
||
onChange={handleChange}
|
||
className={phoneError ? "phone-input-error" : ""}
|
||
/>
|
||
</div>
|
||
|
||
{authError && (
|
||
<span style={{ color: "red", width: "100%" }}>{authError}</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 ? (
|
||
<>
|
||
<Spinner size="small" color="#fff" />
|
||
Adding...
|
||
</>
|
||
) : (
|
||
"Add User"
|
||
)}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</Modal>
|
||
);
|
||
};
|
||
|
||
export default AddUser;
|