Fixed user edit and introduced user enabled/disabled
This commit is contained in:
parent
568f56bf00
commit
a3ad2a8937
@ -25,10 +25,6 @@ export async function POST(request: Request) {
|
|||||||
try {
|
try {
|
||||||
const payload = decodeJwt(token);
|
const payload = decodeJwt(token);
|
||||||
mustChangePassword = payload.MustChangePassword || false;
|
mustChangePassword = payload.MustChangePassword || false;
|
||||||
console.log(
|
|
||||||
"🔍 Current JWT MustChangePassword flag:",
|
|
||||||
mustChangePassword
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("❌ Failed to decode current JWT:", err);
|
console.error("❌ Failed to decode current JWT:", err);
|
||||||
}
|
}
|
||||||
@ -71,10 +67,6 @@ export async function POST(request: Request) {
|
|||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
|
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
console.log("[DEBUG] [CHANGE-PASSWORD] Error response:", {
|
|
||||||
status: resp.status,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ success: false, message: data?.message || "Password change failed" },
|
{ success: false, message: data?.message || "Password change failed" },
|
||||||
{ status: resp.status }
|
{ status: resp.status }
|
||||||
@ -89,17 +81,6 @@ export async function POST(request: Request) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (newToken) {
|
if (newToken) {
|
||||||
try {
|
|
||||||
const payload = decodeJwt(newToken);
|
|
||||||
console.log("🔍 New JWT payload:", payload);
|
|
||||||
console.log(
|
|
||||||
"🔍 must_change_password flag:",
|
|
||||||
payload.must_change_password
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("❌ Failed to decode new JWT:", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Derive maxAge from JWT exp if available; fallback to 12h
|
// Derive maxAge from JWT exp if available; fallback to 12h
|
||||||
let maxAge = 60 * 60 * 12;
|
let maxAge = 60 * 60 * 12;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { formatPhoneDisplay } from "@/app/features/UserRoles/utils";
|
|
||||||
|
|
||||||
const BE_BASE_URL = process.env.BE_BASE_URL || "http://localhost:8583";
|
const BE_BASE_URL = process.env.BE_BASE_URL || "http://localhost:8583";
|
||||||
const COOKIE_NAME = "auth_token";
|
const COOKIE_NAME = "auth_token";
|
||||||
|
|
||||||
// Interface matching the backend RegisterRequest
|
// Interface matching the backend RegisterRequest and frontend IEditUserForm
|
||||||
interface RegisterRequest {
|
interface RegisterRequest {
|
||||||
creator: string;
|
creator: string;
|
||||||
email: string;
|
email: string;
|
||||||
@ -18,23 +17,9 @@ interface RegisterRequest {
|
|||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frontend form interface
|
|
||||||
interface FrontendRegisterForm {
|
|
||||||
email: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
username: string;
|
|
||||||
phone?: string;
|
|
||||||
jobTitle?: string;
|
|
||||||
groups?: string[];
|
|
||||||
merchants?: string[];
|
|
||||||
creator?: string;
|
|
||||||
countryCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
const body: FrontendRegisterForm = await request.json();
|
const body: RegisterRequest = await request.json();
|
||||||
|
|
||||||
// Get the auth token from cookies
|
// Get the auth token from cookies
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
@ -50,35 +35,6 @@ export async function POST(request: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate required fields
|
|
||||||
const requiredFields = ["email", "firstName", "lastName", "username"];
|
|
||||||
const missingFields = requiredFields.filter(
|
|
||||||
field => !body[field as keyof FrontendRegisterForm]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (missingFields.length > 0) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{
|
|
||||||
success: false,
|
|
||||||
message: `Missing required fields: ${missingFields.join(", ")}`,
|
|
||||||
},
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map frontend payload to backend RegisterRequest format
|
|
||||||
const registerPayload: RegisterRequest = {
|
|
||||||
creator: body.creator || "",
|
|
||||||
email: body.email,
|
|
||||||
first_name: body.firstName,
|
|
||||||
groups: body.groups || ["Reader"], // Default to empty array if not provided
|
|
||||||
job_title: body.jobTitle || "Reader",
|
|
||||||
last_name: body.lastName,
|
|
||||||
merchants: body.merchants || ["Win Bot"], // Default to empty array if not provided
|
|
||||||
phone: body.phone ? formatPhoneDisplay(body.phone, body.countryCode) : "",
|
|
||||||
username: body.username,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call backend registration endpoint
|
// Call backend registration endpoint
|
||||||
const resp = await fetch(`${BE_BASE_URL}/api/v1/auth/register`, {
|
const resp = await fetch(`${BE_BASE_URL}/api/v1/auth/register`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -86,15 +42,12 @@ export async function POST(request: Request) {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(registerPayload),
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("[DEBUG] [REGISTER-PAYLOAD]: ", registerPayload);
|
|
||||||
|
|
||||||
// Handle backend response
|
// Handle backend response
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
const errorData = await safeJson(resp);
|
const errorData = await safeJson(resp);
|
||||||
console.log("[DEBUG] [REGISTER-ERROR]: ", errorData);
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
@ -106,8 +59,6 @@ export async function POST(request: Request) {
|
|||||||
|
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
|
|
||||||
console.log("[DEBUG] [REGISTER]: ", data);
|
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@ -23,9 +23,6 @@ export async function POST() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
|
|
||||||
console.log("[DEBUG] [VALIDATE-AUTH][ROUTE][data]: ", data);
|
|
||||||
|
|
||||||
return NextResponse.json(data, { status: resp.status });
|
return NextResponse.json(data, { status: resp.status });
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const message = err instanceof Error ? err.message : "Unknown error";
|
const message = err instanceof Error ? err.message : "Unknown error";
|
||||||
|
|||||||
@ -4,18 +4,79 @@ import { NextResponse } from "next/server";
|
|||||||
const BE_BASE_URL = process.env.BE_BASE_URL || "http://localhost:5000";
|
const BE_BASE_URL = process.env.BE_BASE_URL || "http://localhost:5000";
|
||||||
const COOKIE_NAME = "auth_token";
|
const COOKIE_NAME = "auth_token";
|
||||||
|
|
||||||
export async function PATCH(
|
// Field mapping: snake_case input -> { snake_case for data, PascalCase for fields }
|
||||||
|
// Matches API metadata field_names.users mapping
|
||||||
|
const FIELD_MAPPING: Record<string, { dataKey: string; fieldName: string }> = {
|
||||||
|
id: { dataKey: "id", fieldName: "ID" },
|
||||||
|
email: { dataKey: "email", fieldName: "Email" },
|
||||||
|
first_name: { dataKey: "first_name", fieldName: "FirstName" },
|
||||||
|
last_name: { dataKey: "last_name", fieldName: "LastName" },
|
||||||
|
username: { dataKey: "username", fieldName: "Username" },
|
||||||
|
phone: { dataKey: "phone", fieldName: "Phone" },
|
||||||
|
job_title: { dataKey: "job_title", fieldName: "JobTitle" },
|
||||||
|
password: { dataKey: "password", fieldName: "Password" },
|
||||||
|
temp_link: { dataKey: "temp_link", fieldName: "TempLink" },
|
||||||
|
temp_password: { dataKey: "temp_password", fieldName: "TempPassword" },
|
||||||
|
temp_expiry: { dataKey: "temp_expiry", fieldName: "TempExpiry" },
|
||||||
|
groups: { dataKey: "groups", fieldName: "Groups" },
|
||||||
|
merchants: { dataKey: "merchants", fieldName: "Merchants" },
|
||||||
|
enabled: { dataKey: "enabled", fieldName: "Enabled" },
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms frontend snake_case data to backend format
|
||||||
|
* with data (snake_case) and fields (PascalCase) arrays
|
||||||
|
*/
|
||||||
|
function transformUserUpdateData(updates: Record<string, unknown>): {
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
fields: string[];
|
||||||
|
} {
|
||||||
|
const data: Record<string, unknown> = {};
|
||||||
|
const fields: string[] = [];
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(updates)) {
|
||||||
|
// Skip undefined/null values
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapping = FIELD_MAPPING[key];
|
||||||
|
if (mapping) {
|
||||||
|
// Use the dataKey for the data object (snake_case)
|
||||||
|
data[mapping.dataKey] = value;
|
||||||
|
// Use the fieldName for the fields array (PascalCase)
|
||||||
|
fields.push(mapping.fieldName);
|
||||||
|
} else {
|
||||||
|
// If no mapping exists, use the key as-is (for backwards compatibility)
|
||||||
|
data[key] = value;
|
||||||
|
// Convert snake_case to PascalCase for fields
|
||||||
|
const pascalCase = key
|
||||||
|
.split("_")
|
||||||
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join("");
|
||||||
|
fields.push(pascalCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data, fields };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PUT(
|
||||||
request: Request,
|
request: Request,
|
||||||
{ params }: { params: { id: string } }
|
{ params }: { params: { id: string } }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
console.log("[PATCH /users] - params", params);
|
const { id } = await params;
|
||||||
const { id } = params;
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|
||||||
|
// Transform the request body to match backend format
|
||||||
|
const transformedBody = transformUserUpdateData(body);
|
||||||
|
console.log("[PUT /api/v1/users/{id}] - transformed body", transformedBody);
|
||||||
|
|
||||||
// Get the auth token from cookies
|
// Get the auth token from cookies
|
||||||
const { cookies } = await import("next/headers");
|
const { cookies } = await import("next/headers");
|
||||||
const cookieStore = cookies();
|
const cookieStore = await cookies();
|
||||||
const token = (await cookieStore).get(COOKIE_NAME)?.value;
|
const token = cookieStore.get(COOKIE_NAME)?.value;
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@ -24,21 +85,23 @@ export async function PATCH(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${BE_BASE_URL}/users/${id}`, {
|
// According to swagger: /api/v1/users/{id}
|
||||||
method: "PATCH",
|
const response = await fetch(`${BE_BASE_URL}/api/v1/users/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(transformedBody),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return NextResponse.json(data, { status: response.status });
|
return NextResponse.json(data, { status: response.status });
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
console.error("Proxy PATCH /users error:", err);
|
const errorMessage =
|
||||||
|
err instanceof Error ? err.message : "Unknown error occurred";
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ message: "Internal server error", error: err.message },
|
{ message: "Internal server error", error: errorMessage },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -49,7 +112,7 @@ export async function DELETE(
|
|||||||
{ params }: { params: { id: string } }
|
{ params }: { params: { id: string } }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const { id } = params;
|
const { id } = await params;
|
||||||
const { cookies } = await import("next/headers");
|
const { cookies } = await import("next/headers");
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const token = cookieStore.get(COOKIE_NAME)?.value;
|
const token = cookieStore.get(COOKIE_NAME)?.value;
|
||||||
@ -70,7 +133,7 @@ export async function DELETE(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Some backends return empty body for DELETE; handle safely
|
// Some backends return empty body for DELETE; handle safely
|
||||||
let data: any = null;
|
let data: unknown = null;
|
||||||
try {
|
try {
|
||||||
data = await response.json();
|
data = await response.json();
|
||||||
} catch {
|
} catch {
|
||||||
@ -80,10 +143,12 @@ export async function DELETE(
|
|||||||
return NextResponse.json(data ?? { success: response.ok }, {
|
return NextResponse.json(data ?? { success: response.ok }, {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
console.error("Proxy DELETE /api/v1/users/{id} error:", err);
|
console.error("Proxy DELETE /api/v1/users/{id} error:", err);
|
||||||
|
const errorMessage =
|
||||||
|
err instanceof Error ? err.message : "Unknown error occurred";
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ message: "Internal server error", error: err.message },
|
{ message: "Internal server error", error: errorMessage },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,6 @@ export async function GET(request: Request) {
|
|||||||
cache: "no-store",
|
cache: "no-store",
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("[DEBUG] - Response", response);
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return NextResponse.json(data, { status: response.status });
|
return NextResponse.json(data, { status: response.status });
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
|||||||
@ -17,6 +17,8 @@ const Users: React.FC<UsersProps> = ({ users }) => {
|
|||||||
const [showAddUser, setShowAddUser] = useState(false);
|
const [showAddUser, setShowAddUser] = useState(false);
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
|
||||||
|
console.log("[Users] - users", users);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<UserTopBar
|
<UserTopBar
|
||||||
@ -30,7 +32,6 @@ const Users: React.FC<UsersProps> = ({ users }) => {
|
|||||||
<Card key={user.id} sx={{ mb: 2 }}>
|
<Card key={user.id} sx={{ mb: 2 }}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6">{user.username}</Typography>
|
<Typography variant="h6">{user.username}</Typography>
|
||||||
|
|
||||||
<Stack direction="row" spacing={1} mt={1}>
|
<Stack direction="row" spacing={1} mt={1}>
|
||||||
<UserRoleCard user={user} />
|
<UserRoleCard user={user} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { AppDispatch, RootState } from "@/app/redux/store";
|
import { AppDispatch, RootState } from "@/app/redux/store";
|
||||||
import { updateUserDetails } from "@/app/redux/auth/authSlice";
|
import { updateUserDetails } from "@/app/redux/user/userSlice";
|
||||||
|
|
||||||
const SettingsPersonalInfo: React.FC = () => {
|
const SettingsPersonalInfo: React.FC = () => {
|
||||||
const user = useSelector((state: RootState) => state.auth.user);
|
const user = useSelector((state: RootState) => state.auth.user);
|
||||||
|
|||||||
@ -26,14 +26,13 @@ const AddUser: React.FC<AddUserProps> = ({ open, onClose }) => {
|
|||||||
);
|
);
|
||||||
const [form, setForm] = useState<IEditUserForm>({
|
const [form, setForm] = useState<IEditUserForm>({
|
||||||
username: "",
|
username: "",
|
||||||
firstName: "",
|
first_name: "",
|
||||||
lastName: "",
|
last_name: "",
|
||||||
email: "",
|
email: "",
|
||||||
phone: "",
|
phone: "",
|
||||||
role: "",
|
|
||||||
merchants: [],
|
merchants: [],
|
||||||
groups: [],
|
groups: [],
|
||||||
jobTitle: "",
|
job_title: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [phoneError, setPhoneError] = useState("");
|
const [phoneError, setPhoneError] = useState("");
|
||||||
@ -94,12 +93,12 @@ const AddUser: React.FC<AddUserProps> = ({ open, onClose }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!form.firstName ||
|
!form.first_name ||
|
||||||
!form.lastName ||
|
!form.last_name ||
|
||||||
!form.email ||
|
!form.email ||
|
||||||
form.merchants.length === 0 ||
|
form.merchants.length === 0 ||
|
||||||
form.groups.length === 0 ||
|
form.groups.length === 0 ||
|
||||||
!form.jobTitle
|
!form.job_title
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -136,16 +135,16 @@ const AddUser: React.FC<AddUserProps> = ({ open, onClose }) => {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
name="firstName"
|
name="first_name"
|
||||||
placeholder="First Name"
|
placeholder="First Name"
|
||||||
value={form.firstName}
|
value={form.first_name}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
name="lastName"
|
name="last_name"
|
||||||
placeholder="Last Name"
|
placeholder="Last Name"
|
||||||
value={form.lastName}
|
value={form.last_name}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -235,8 +234,8 @@ const AddUser: React.FC<AddUserProps> = ({ open, onClose }) => {
|
|||||||
<div className="array-field-container">
|
<div className="array-field-container">
|
||||||
<label>Job Title:</label>
|
<label>Job Title:</label>
|
||||||
<select
|
<select
|
||||||
name="jobTitle"
|
name="job_title"
|
||||||
value={form.jobTitle}
|
value={form.job_title}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
required
|
required
|
||||||
className="add-user__select"
|
className="add-user__select"
|
||||||
|
|||||||
@ -4,7 +4,18 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
|
||||||
input {
|
&__status-toggle {
|
||||||
|
flex-basis: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f7f7f8;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
&__select {
|
||||||
flex: 1 1 20%;
|
flex: 1 1 20%;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -40,7 +51,62 @@
|
|||||||
button:last-child {
|
button:last-child {
|
||||||
color: var(--button-secondary);
|
color: var(--button-secondary);
|
||||||
border-color: var(--button-secondary);
|
border-color: var(--button-secondary);
|
||||||
margin-left: 8;
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.array-field-container {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
min-width: 200px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-items {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
background: #e3f2fd;
|
||||||
|
color: #1976d2;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border: 1px solid #bbdefb;
|
||||||
|
|
||||||
|
.remove-item {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #1976d2;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(25, 118, 210, 0.1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,83 +1,169 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { AppDispatch, RootState } from "@/app/redux/store";
|
||||||
|
import { updateUserDetails } from "@/app/redux/user/userSlice";
|
||||||
import { IEditUserForm, EditUserField } from "../User.interfaces";
|
import { IEditUserForm, EditUserField } from "../User.interfaces";
|
||||||
import { editUser } from "@/services/roles.services";
|
|
||||||
import { IUser } from "../../Pages/Admin/Users/interfaces";
|
import { IUser } from "../../Pages/Admin/Users/interfaces";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { Switch, FormControlLabel } from "@mui/material";
|
||||||
import "./EditUser.scss";
|
import "./EditUser.scss";
|
||||||
|
import { selectAppMetadata } from "@/app/redux/metadata/selectors";
|
||||||
|
|
||||||
const EditUser = ({ user }: { user: IUser }) => {
|
const EditUser = ({ user }: { user: IUser }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { username, last_name, email, groups, phone } = user;
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const { status } = useSelector((state: RootState) => state.user);
|
||||||
|
// Get original Metadata from Redux store
|
||||||
|
const data = useSelector(selectAppMetadata);
|
||||||
|
const {
|
||||||
|
merchants: metadataMerchants = [],
|
||||||
|
groups: metadataGroups = [],
|
||||||
|
job_titles: metadataJobTitles = [],
|
||||||
|
} = data || {};
|
||||||
|
|
||||||
|
// Get original user data from the API response
|
||||||
|
const {
|
||||||
|
username,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
email,
|
||||||
|
groups,
|
||||||
|
phone,
|
||||||
|
enabled,
|
||||||
|
job_title,
|
||||||
|
merchants,
|
||||||
|
} = user;
|
||||||
|
|
||||||
const [form, setForm] = React.useState<IEditUserForm>({
|
const [form, setForm] = React.useState<IEditUserForm>({
|
||||||
firstName: username || "",
|
first_name: first_name || "",
|
||||||
lastName: last_name || "",
|
last_name: last_name || "",
|
||||||
email: email || "",
|
email: email || "",
|
||||||
role: roles[0] || "",
|
|
||||||
phone: phone || "",
|
phone: phone || "",
|
||||||
groups: groups || [],
|
groups: groups || [],
|
||||||
merchants: merchants || [],
|
merchants: merchants || [],
|
||||||
jobTitle: jobTitle || "",
|
job_title: job_title || "",
|
||||||
username: username || "",
|
username: username || "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||||
|
) => {
|
||||||
const name = e.target.name as EditUserField;
|
const name = e.target.name as EditUserField;
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
|
|
||||||
if (name === "phone") {
|
if (name === "phone") {
|
||||||
const filtered = value.replace(/[^0-9+\-\s()]/g, "");
|
const filtered = value.replace(/[^0-9+\-\s()]/g, "");
|
||||||
setForm(prev => ({
|
setForm(prev => ({ ...prev, phone: filtered }));
|
||||||
...prev,
|
return;
|
||||||
phone: filtered,
|
}
|
||||||
}));
|
|
||||||
|
// 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 {
|
} else {
|
||||||
setForm(prev => ({
|
// Handle single value fields
|
||||||
...prev,
|
setForm(prev => ({ ...prev, [name]: value }));
|
||||||
[name]: value,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResetForm = () => {
|
const handleUpdate = async (
|
||||||
setForm({
|
e?: React.FormEvent<HTMLFormElement> | Partial<{ enabled: boolean }>
|
||||||
firstName: "",
|
) => {
|
||||||
lastName: "",
|
if (e && "preventDefault" in e) e.preventDefault();
|
||||||
email: "",
|
|
||||||
role: "",
|
// Check if this was a toggle-only update
|
||||||
phone: "",
|
const isToggle = !e || ("enabled" in e && Object.keys(e).length === 1);
|
||||||
groups: [],
|
|
||||||
merchants: [],
|
const updates: Record<string, unknown> = {};
|
||||||
jobTitle: "",
|
|
||||||
username: "",
|
// Compare form fields vs original user object
|
||||||
|
Object.entries(form).forEach(([key, value]) => {
|
||||||
|
const originalValue = (user as unknown as Record<string, unknown>)[key];
|
||||||
|
// Only include changed values and skip empty ones
|
||||||
|
if (
|
||||||
|
value !== "" &&
|
||||||
|
JSON.stringify(value) !== JSON.stringify(originalValue)
|
||||||
|
) {
|
||||||
|
updates[key] = value;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
// Handle enabled toggle separately
|
||||||
e.preventDefault();
|
if (isToggle) {
|
||||||
|
updates.enabled = !(enabled ?? true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing changed — no need to call API
|
||||||
|
if (Object.keys(updates).length === 0) {
|
||||||
|
toast("No changes detected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[handleUpdate] - updates", updates);
|
||||||
try {
|
try {
|
||||||
await editUser(user.id, form);
|
const resultAction = await dispatch(
|
||||||
router.refresh(); // <- refreshes the page (SSR re-runs)
|
updateUserDetails({ id: user.id, updates })
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
);
|
||||||
} catch (err: any) {
|
|
||||||
console.log(err.message || "Error creating role");
|
if (updateUserDetails.fulfilled.match(resultAction)) {
|
||||||
// setError(err.message || "Error creating role");
|
toast.success(resultAction.payload.message);
|
||||||
|
router.refresh();
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
(resultAction.payload as string) || "Failed to update user"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to update user:", err);
|
||||||
|
toast.error("An unexpected error occurred");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loading = status === "loading";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="edit-user" onSubmit={handleSubmit}>
|
<form className="edit-user" onSubmit={handleUpdate}>
|
||||||
|
<div className="edit-user__status-toggle">
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
checked={enabled ?? true}
|
||||||
|
onChange={() => handleUpdate()}
|
||||||
|
disabled={loading}
|
||||||
|
color={(enabled ?? true) ? "success" : "default"}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={(enabled ?? true) ? "User Enabled" : "User Disabled"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="First Name"
|
placeholder="First Name"
|
||||||
name="firstName"
|
name="first_name"
|
||||||
value={form.firstName}
|
value={form.first_name}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Last Name"
|
placeholder="Last Name"
|
||||||
name="lastName"
|
name="last_name"
|
||||||
value={form.lastName}
|
value={form.last_name}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@ -89,11 +175,104 @@ const EditUser = ({ user }: { user: IUser }) => {
|
|||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Role"
|
placeholder="Username"
|
||||||
name="role"
|
name="username"
|
||||||
value={form.role}
|
value={form.username}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
<div className="array-field-container">
|
||||||
|
<label>Merchants:</label>
|
||||||
|
<select
|
||||||
|
name="merchants"
|
||||||
|
value=""
|
||||||
|
onChange={handleChange}
|
||||||
|
className="edit-user__select"
|
||||||
|
>
|
||||||
|
<option value="">Select Merchant</option>
|
||||||
|
{metadataMerchants.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="edit-user__select"
|
||||||
|
>
|
||||||
|
<option value="">Select Group</option>
|
||||||
|
{metadataGroups.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}
|
||||||
|
className="edit-user__select"
|
||||||
|
>
|
||||||
|
<option value="">Select Job Title</option>
|
||||||
|
{metadataJobTitles?.map((job_title: string) => (
|
||||||
|
<option key={job_title} value={job_title}>
|
||||||
|
{job_title}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
placeholder="Phone"
|
placeholder="Phone"
|
||||||
@ -105,10 +284,10 @@ const EditUser = ({ user }: { user: IUser }) => {
|
|||||||
inputMode="tel"
|
inputMode="tel"
|
||||||
autoComplete="tel"
|
autoComplete="tel"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="edit-user__button-container">
|
<div className="edit-user__button-container">
|
||||||
<button type="submit">Save</button>
|
<button type="submit" disabled={loading}>
|
||||||
<button type="button" onClick={handleResetForm}>
|
{loading ? "Saving..." : "Save"}
|
||||||
Clear
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,22 +1,20 @@
|
|||||||
export interface IEditUserForm {
|
export interface IEditUserForm {
|
||||||
username: string;
|
username: string;
|
||||||
firstName: string;
|
first_name: string;
|
||||||
lastName: string;
|
last_name: string;
|
||||||
email: string;
|
email: string;
|
||||||
role: string;
|
|
||||||
phone: string;
|
phone: string;
|
||||||
merchants: string[];
|
merchants: string[];
|
||||||
groups: string[];
|
groups: string[];
|
||||||
jobTitle: string;
|
job_title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EditUserField =
|
export type EditUserField =
|
||||||
| "merchants"
|
| "merchants"
|
||||||
| "groups"
|
| "groups"
|
||||||
| "jobTitle"
|
| "job_title"
|
||||||
| "username"
|
| "username"
|
||||||
| "firstName"
|
| "first_name"
|
||||||
| "lastName"
|
| "last_name"
|
||||||
| "email"
|
| "email"
|
||||||
| "role"
|
|
||||||
| "phone";
|
| "phone";
|
||||||
|
|||||||
@ -20,9 +20,9 @@ import {
|
|||||||
History,
|
History,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import EditUser from "./EditUser/EditUser";
|
import EditUser from "./EditUser/EditUser";
|
||||||
import "./User.scss";
|
|
||||||
import { IUser } from "../Pages/Admin/Users/interfaces";
|
import { IUser } from "../Pages/Admin/Users/interfaces";
|
||||||
import DeleteUser from "./DeleteUser/DeleteUser";
|
import DeleteUser from "./DeleteUser/DeleteUser";
|
||||||
|
import "./User.scss";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: IUser;
|
user: IUser;
|
||||||
@ -32,6 +32,7 @@ export default function UserRoleCard({ user }: Props) {
|
|||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [openDeleteUser, setOpenDeleteUser] = useState(false);
|
const [openDeleteUser, setOpenDeleteUser] = useState(false);
|
||||||
const { username, first_name, last_name, email, groups } = user;
|
const { username, first_name, last_name, email, groups } = user;
|
||||||
|
|
||||||
const handleEditClick = () => {
|
const handleEditClick = () => {
|
||||||
setIsEditing(!isEditing);
|
setIsEditing(!isEditing);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -219,38 +219,6 @@ export const addUser = createAsyncThunk<
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------- Update User Details ----------------
|
|
||||||
export const updateUserDetails = createAsyncThunk<
|
|
||||||
ThunkSuccess<{ user: IUserResponse }>,
|
|
||||||
{ id: string; updates: Partial<IUserResponse> },
|
|
||||||
{ rejectValue: ThunkError }
|
|
||||||
>("auth/updateUserDetails", async ({ id, updates }, { rejectWithValue }) => {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/api/dashboard/admin/users/${id}`, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(updates),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
return rejectWithValue(data.message || "Failed to update user details");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
message: data.message || "User details updated successfully",
|
|
||||||
user: data.user,
|
|
||||||
};
|
|
||||||
} catch (err: unknown) {
|
|
||||||
return rejectWithValue(
|
|
||||||
(err as Error).message || "Network error during user update"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---------------- Slice ----------------
|
// ---------------- Slice ----------------
|
||||||
const authSlice = createSlice({
|
const authSlice = createSlice({
|
||||||
name: "auth",
|
name: "auth",
|
||||||
@ -359,21 +327,6 @@ const authSlice = createSlice({
|
|||||||
state.error = action.payload as string;
|
state.error = action.payload as string;
|
||||||
state.authMessage = action.payload as string;
|
state.authMessage = action.payload as string;
|
||||||
state.addedUser = null;
|
state.addedUser = null;
|
||||||
})
|
|
||||||
// Update User Details
|
|
||||||
.addCase(updateUserDetails.pending, state => {
|
|
||||||
state.status = "loading";
|
|
||||||
state.authMessage = "Updating user details...";
|
|
||||||
})
|
|
||||||
.addCase(updateUserDetails.fulfilled, (state, action) => {
|
|
||||||
state.status = "succeeded";
|
|
||||||
state.user = action.payload.user;
|
|
||||||
state.authMessage = action.payload.message;
|
|
||||||
})
|
|
||||||
.addCase(updateUserDetails.rejected, (state, action) => {
|
|
||||||
state.status = "failed";
|
|
||||||
state.error = action.payload as string;
|
|
||||||
state.authMessage = action.payload as string;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -63,7 +63,7 @@ export const editUser = createAsyncThunk<
|
|||||||
>("user/editUser", async ({ id, updates }, { rejectWithValue }) => {
|
>("user/editUser", async ({ id, updates }, { rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/dashboard/admin/users/${id}`, {
|
const res = await fetch(`/api/dashboard/admin/users/${id}`, {
|
||||||
method: "PATCH",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
@ -87,6 +87,38 @@ export const editUser = createAsyncThunk<
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------- Update User Details ----------------
|
||||||
|
export const updateUserDetails = createAsyncThunk<
|
||||||
|
ThunkSuccess<{ user: IUserResponse }>,
|
||||||
|
{ id: string; updates: Record<string, unknown> },
|
||||||
|
{ rejectValue: ThunkError }
|
||||||
|
>("user/updateUserDetails", async ({ id, updates }, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/dashboard/admin/users/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(updates),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
return rejectWithValue(data.message || "Failed to update user details");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: data.message || "User details updated successfully",
|
||||||
|
user: data.user,
|
||||||
|
};
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return rejectWithValue(
|
||||||
|
(err as Error).message || "Network error during user update"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ---------------- Delete User ----------------
|
// ---------------- Delete User ----------------
|
||||||
export const deleteUser = createAsyncThunk<
|
export const deleteUser = createAsyncThunk<
|
||||||
ThunkSuccess<{ id: string }>,
|
ThunkSuccess<{ id: string }>,
|
||||||
@ -176,6 +208,25 @@ const userSlice = createSlice({
|
|||||||
state.message = action.payload as string;
|
state.message = action.payload as string;
|
||||||
state.editedUser = null;
|
state.editedUser = null;
|
||||||
})
|
})
|
||||||
|
// Update User Details
|
||||||
|
.addCase(updateUserDetails.pending, state => {
|
||||||
|
state.status = "loading";
|
||||||
|
state.message = "Updating user details...";
|
||||||
|
state.editedUser = null;
|
||||||
|
state.error = null;
|
||||||
|
})
|
||||||
|
.addCase(updateUserDetails.fulfilled, (state, action) => {
|
||||||
|
state.status = "succeeded";
|
||||||
|
state.message = action.payload.message;
|
||||||
|
state.editedUser = action.payload.user;
|
||||||
|
state.error = null;
|
||||||
|
})
|
||||||
|
.addCase(updateUserDetails.rejected, (state, action) => {
|
||||||
|
state.status = "failed";
|
||||||
|
state.error = action.payload as string;
|
||||||
|
state.message = action.payload as string;
|
||||||
|
state.editedUser = null;
|
||||||
|
})
|
||||||
// Delete User
|
// Delete User
|
||||||
.addCase(deleteUser.pending, state => {
|
.addCase(deleteUser.pending, state => {
|
||||||
state.status = "loading";
|
state.status = "loading";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user