Fixed creator bug when adding new user
This commit is contained in:
parent
483f2656da
commit
eff7f62299
@ -30,6 +30,7 @@ interface FrontendRegisterForm {
|
|||||||
groups?: string[];
|
groups?: string[];
|
||||||
merchants?: string[];
|
merchants?: string[];
|
||||||
creator?: string;
|
creator?: string;
|
||||||
|
countryCode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.2);
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
max-width: 60vw;
|
max-width: 65vw;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 2rem 1.5rem 1.5rem 1.5rem;
|
padding: 2rem 1.5rem 1.5rem 1.5rem;
|
||||||
|
|||||||
48
app/components/Spinner/Spinner.scss
Normal file
48
app/components/Spinner/Spinner.scss
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-top: 2px solid #1976d2;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
|
||||||
|
&--small {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--medium {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--large {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__inner {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 60%;
|
||||||
|
height: 60%;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-top: 1px solid currentColor;
|
||||||
|
animation: spin 0.8s linear infinite reverse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/components/Spinner/Spinner.tsx
Normal file
23
app/components/Spinner/Spinner.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "./Spinner.scss";
|
||||||
|
|
||||||
|
interface SpinnerProps {
|
||||||
|
size?: "small" | "medium" | "large";
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Spinner: React.FC<SpinnerProps> = ({
|
||||||
|
size = "medium",
|
||||||
|
color = "#1976d2",
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`spinner spinner--${size}`}
|
||||||
|
style={{ borderTopColor: color }}
|
||||||
|
>
|
||||||
|
<div className="spinner__inner"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Spinner;
|
||||||
@ -67,6 +67,15 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button:first-child {
|
button:first-child {
|
||||||
|
|||||||
@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { AppDispatch } from "@/app/redux/store";
|
||||||
import "./AddUser.scss";
|
import "./AddUser.scss";
|
||||||
import { addUser } from "@/services/roles.services";
|
import { addUser } from "@/app/redux/auth/authSlice";
|
||||||
import { IEditUserForm } from "../User.interfaces";
|
import { IEditUserForm } from "../User.interfaces";
|
||||||
import { COUNTRY_CODES } from "../constants";
|
import { COUNTRY_CODES } from "../constants";
|
||||||
import { formatPhoneDisplay, validatePhone } from "../utils";
|
import { formatPhoneDisplay, validatePhone } from "../utils";
|
||||||
|
import Spinner from "../../../components/Spinner/Spinner";
|
||||||
|
import { RootState } from "@/app/redux/store";
|
||||||
|
|
||||||
interface AddUserFormProps {
|
interface AddUserFormProps {
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
@ -14,6 +18,11 @@ interface AddUserFormProps {
|
|||||||
|
|
||||||
const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
const { status, error: authError } = useSelector(
|
||||||
|
(state: RootState) => state.auth
|
||||||
|
);
|
||||||
|
|
||||||
const [form, setForm] = useState<IEditUserForm>({
|
const [form, setForm] = useState<IEditUserForm>({
|
||||||
username: "",
|
username: "",
|
||||||
firstName: "",
|
firstName: "",
|
||||||
@ -26,11 +35,11 @@ const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
|||||||
jobTitle: "",
|
jobTitle: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [error, setError] = useState("");
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [phoneError, setPhoneError] = useState("");
|
const [phoneError, setPhoneError] = useState("");
|
||||||
const [countryCode, setCountryCode] = useState("+1");
|
const [countryCode, setCountryCode] = useState("+1");
|
||||||
|
|
||||||
|
const loading = status === "loading";
|
||||||
|
|
||||||
const handleChange = (
|
const handleChange = (
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||||
) => {
|
) => {
|
||||||
@ -78,7 +87,6 @@ const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
|||||||
|
|
||||||
// Validate phone number if provided
|
// Validate phone number if provided
|
||||||
if (form.phone && phoneError) {
|
if (form.phone && phoneError) {
|
||||||
setError("Please fix phone number errors before submitting.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,28 +98,22 @@ const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
|||||||
form.groups.length === 0 ||
|
form.groups.length === 0 ||
|
||||||
!form.jobTitle
|
!form.jobTitle
|
||||||
) {
|
) {
|
||||||
setError("Please fill in all required fields.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
setError("");
|
|
||||||
|
|
||||||
// Format phone number with country code before submission
|
// Format phone number with country code before submission
|
||||||
const formattedForm = {
|
const formattedForm = {
|
||||||
...form,
|
...form,
|
||||||
phone: form.phone ? formatPhoneDisplay(form.phone, countryCode) : "",
|
phone: form.phone ? formatPhoneDisplay(form.phone, countryCode) : "",
|
||||||
};
|
};
|
||||||
|
|
||||||
await addUser(formattedForm);
|
try {
|
||||||
|
await dispatch(addUser(formattedForm));
|
||||||
if (onSuccess) onSuccess();
|
if (onSuccess) onSuccess();
|
||||||
router.refresh(); // <- refreshes the page (SSR re-runs)
|
router.refresh(); // <- refreshes the page (SSR re-runs)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
} catch (err) {
|
||||||
} catch (err: any) {
|
// Error is handled by Redux state
|
||||||
setError(err.message || "Something went wrong.");
|
console.error("Failed to add user:", err);
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -255,7 +257,9 @@ const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && <span style={{ color: "red", width: "100%" }}>{error}</span>}
|
{authError && (
|
||||||
|
<span style={{ color: "red", width: "100%" }}>{authError}</span>
|
||||||
|
)}
|
||||||
<span
|
<span
|
||||||
className="phone-error-message"
|
className="phone-error-message"
|
||||||
style={{
|
style={{
|
||||||
@ -269,7 +273,14 @@ const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
|||||||
|
|
||||||
<div className="add-user__button-container">
|
<div className="add-user__button-container">
|
||||||
<button type="submit" disabled={loading}>
|
<button type="submit" disabled={loading}>
|
||||||
{loading ? "Adding..." : "Add User"}
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<Spinner size="small" color="#fff" />
|
||||||
|
Adding...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"Add User"
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { ThunkSuccess, ThunkError, IUserResponse } from "../types";
|
import { ThunkSuccess, ThunkError, IUserResponse } from "../types";
|
||||||
|
import { IEditUserForm } from "../../features/UserRoles/User.interfaces";
|
||||||
|
import { RootState } from "../store";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
interface TokenInfo {
|
interface TokenInfo {
|
||||||
@ -175,6 +177,39 @@ export const validateAuth = createAsyncThunk<
|
|||||||
|
|
||||||
// TODO - Creaye a new thunk to update the user stuff
|
// TODO - Creaye a new thunk to update the user stuff
|
||||||
|
|
||||||
|
// ---------------- Add User ----------------
|
||||||
|
export const addUser = createAsyncThunk<
|
||||||
|
ThunkSuccess<{ user: IUserResponse }>,
|
||||||
|
IEditUserForm,
|
||||||
|
{ rejectValue: ThunkError; state: RootState }
|
||||||
|
>("auth/addUser", async (userData, { rejectWithValue, getState }) => {
|
||||||
|
try {
|
||||||
|
const state = getState();
|
||||||
|
const currentUserId = state.auth.user?.id;
|
||||||
|
console.log("[DEBUG] [ADD-USER] [currentUserId]: ", currentUserId);
|
||||||
|
const res = await fetch("/api/auth/register", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ ...userData, creator: currentUserId || "" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
return rejectWithValue(data.message || "Failed to create user");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: data.message || "User created successfully",
|
||||||
|
user: data.user,
|
||||||
|
};
|
||||||
|
} catch (err: unknown) {
|
||||||
|
return rejectWithValue(
|
||||||
|
(err as Error).message || "Network error during user creation"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ---------------- Update User Details ----------------
|
// ---------------- Update User Details ----------------
|
||||||
export const updateUserDetails = createAsyncThunk<
|
export const updateUserDetails = createAsyncThunk<
|
||||||
ThunkSuccess<{ user: IUserResponse }>,
|
ThunkSuccess<{ user: IUserResponse }>,
|
||||||
@ -297,6 +332,20 @@ const authSlice = createSlice({
|
|||||||
state.tokenInfo = null;
|
state.tokenInfo = null;
|
||||||
state.error = action.payload as string;
|
state.error = action.payload as string;
|
||||||
})
|
})
|
||||||
|
// Add User
|
||||||
|
.addCase(addUser.pending, state => {
|
||||||
|
state.status = "loading";
|
||||||
|
state.authMessage = "Creating user...";
|
||||||
|
})
|
||||||
|
.addCase(addUser.fulfilled, (state, action) => {
|
||||||
|
state.status = "succeeded";
|
||||||
|
state.authMessage = action.payload.message;
|
||||||
|
})
|
||||||
|
.addCase(addUser.rejected, (state, action) => {
|
||||||
|
state.status = "failed";
|
||||||
|
state.error = action.payload as string;
|
||||||
|
state.authMessage = action.payload as string;
|
||||||
|
})
|
||||||
// Update User Details
|
// Update User Details
|
||||||
.addCase(updateUserDetails.pending, state => {
|
.addCase(updateUserDetails.pending, state => {
|
||||||
state.status = "loading";
|
state.status = "loading";
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
import { IEditUserForm } from "@/app/features/UserRoles/User.interfaces";
|
import { IEditUserForm } from "@/app/features/UserRoles/User.interfaces";
|
||||||
|
|
||||||
export async function addUser(data: IEditUserForm) {
|
// export async function addUser(data: IEditUserForm) {
|
||||||
const res = await fetch("/api/auth/register", {
|
// const res = await fetch("/api/auth/register", {
|
||||||
method: "POST",
|
// method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
// headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(data),
|
// body: JSON.stringify(data),
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (!res.ok) {
|
// if (!res.ok) {
|
||||||
throw new Error("Failed to create role");
|
// throw new Error("Failed to create role");
|
||||||
}
|
// }
|
||||||
|
|
||||||
return res.json(); // or return type depending on your backend
|
// return res.json(); // or return type depending on your backend
|
||||||
}
|
// }
|
||||||
export async function editUser(id: string, data: IEditUserForm) {
|
export async function editUser(id: string, data: IEditUserForm) {
|
||||||
console.log("[editUser] - id", id, data);
|
console.log("[editUser] - id", id, data);
|
||||||
const res = await fetch(`/api/dashboard/admin/users/${id}`, {
|
const res = await fetch(`/api/dashboard/admin/users/${id}`, {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user