Added New User Registration Detail Modal
This commit is contained in:
parent
fe6ed86a76
commit
be1df88e75
@ -1,9 +1,9 @@
|
||||
import Users from "@/app/features/Pages/Admin/Users/users";
|
||||
|
||||
export default async function BackOfficeUsersPage() {
|
||||
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL
|
||||
? `${process.env.NEXT_PUBLIC_BASE_URL}`
|
||||
: "http://localhost:4000";
|
||||
// const baseUrl = process.env.NEXT_PUBLIC_BASE_URL
|
||||
// ? `${process.env.NEXT_PUBLIC_BASE_URL}`
|
||||
// : "http://localhost:4000";
|
||||
// const res = await fetch(`${baseUrl}/api/dashboard/admin/users`, {
|
||||
// cache: "no-store", // 👈 disables caching for SSR freshness
|
||||
// });
|
||||
|
||||
@ -3,9 +3,11 @@ import React, { useState } from "react";
|
||||
import { Card, CardContent, Typography, Stack } from "@mui/material";
|
||||
import { IUser } from "./interfaces";
|
||||
import UserTopBar from "@/app/features/UserRoles/AddUser/AddUserButton";
|
||||
import Modal from "@/app/components/Modal/Modal";
|
||||
import UserRoleCard from "@/app/features/UserRoles/userRoleCard";
|
||||
import AddUser from "@/app/features/UserRoles/AddUser/AddUser";
|
||||
import { setSidebarOpen } from "@/app/redux/ui/uiSlice";
|
||||
import { AppDispatch } from "@/app/redux/types";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
interface UsersProps {
|
||||
users: IUser[];
|
||||
@ -13,37 +15,38 @@ interface UsersProps {
|
||||
|
||||
const Users: React.FC<UsersProps> = ({ users }) => {
|
||||
const [showAddUser, setShowAddUser] = useState(false);
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<UserTopBar onAddUser={() => setShowAddUser(true)} />
|
||||
{users.map((user: IUser) => (
|
||||
<Card key={user.id} sx={{ mb: 2 }}>
|
||||
<CardContent>
|
||||
<Typography variant="h6">{user.username}</Typography>
|
||||
<Typography variant="body2">
|
||||
Merchant ID: {user.merchantId}
|
||||
</Typography>
|
||||
<UserTopBar
|
||||
onAddUser={() => {
|
||||
setShowAddUser(true);
|
||||
dispatch(setSidebarOpen(false));
|
||||
}}
|
||||
/>
|
||||
{users?.length > 0 &&
|
||||
users.map((user: IUser) => (
|
||||
<Card key={user.id} sx={{ mb: 2 }}>
|
||||
<CardContent>
|
||||
<Typography variant="h6">{user.username}</Typography>
|
||||
<Typography variant="body2">
|
||||
Merchant ID: {user.merchantId}
|
||||
</Typography>
|
||||
|
||||
<Stack direction="row" spacing={1} mt={1}>
|
||||
<UserRoleCard
|
||||
user={user}
|
||||
isAdmin={true}
|
||||
lastLogin="small"
|
||||
merchants={[]} // merchants={Numberuser.allowedMerchantIds}
|
||||
/>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
<Stack direction="row" spacing={1} mt={1}>
|
||||
<UserRoleCard
|
||||
user={user}
|
||||
isAdmin={true}
|
||||
lastLogin="small"
|
||||
merchants={[]} // merchants={Numberuser.allowedMerchantIds}
|
||||
/>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
<Modal
|
||||
open={showAddUser}
|
||||
onClose={() => setShowAddUser(false)}
|
||||
title="Add User"
|
||||
>
|
||||
<AddUser />
|
||||
</Modal>
|
||||
<AddUser open={showAddUser} onClose={() => setShowAddUser(false)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -11,18 +11,20 @@ import { COUNTRY_CODES } from "../constants";
|
||||
import { formatPhoneDisplay, validatePhone } from "../utils";
|
||||
import Spinner from "../../../components/Spinner/Spinner";
|
||||
import { RootState } from "@/app/redux/store";
|
||||
import toast from "react-hot-toast";
|
||||
import Modal from "@/app/components/Modal/Modal";
|
||||
|
||||
interface AddUserFormProps {
|
||||
onSuccess?: () => void;
|
||||
interface AddUserProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
||||
const router = useRouter();
|
||||
const AddUser: React.FC<AddUserProps> = ({ open, onClose }) => {
|
||||
// const router = useRouter();
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const { status, error: authError } = useSelector(
|
||||
(state: RootState) => state.auth
|
||||
);
|
||||
|
||||
const [form, setForm] = useState<IEditUserForm>({
|
||||
username: "",
|
||||
firstName: "",
|
||||
@ -108,9 +110,14 @@ const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
||||
};
|
||||
|
||||
try {
|
||||
await dispatch(addUser(formattedForm));
|
||||
if (onSuccess) onSuccess();
|
||||
router.refresh(); // <- refreshes the page (SSR re-runs)
|
||||
const resultAction = await dispatch(addUser(formattedForm));
|
||||
const result = addUser.fulfilled.match(resultAction);
|
||||
|
||||
if (result && resultAction.payload.success) {
|
||||
toast.success(resultAction.payload.message);
|
||||
// router.refresh();
|
||||
onClose();
|
||||
}
|
||||
} catch (err) {
|
||||
// Error is handled by Redux state
|
||||
console.error("Failed to add user:", err);
|
||||
@ -118,173 +125,175 @@ const AddUserForm: React.FC<AddUserFormProps> = ({ onSuccess }) => {
|
||||
};
|
||||
|
||||
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=""
|
||||
<Modal open={open} onClose={onClose} title="Add User">
|
||||
<form className="add-user" onSubmit={handleSubmit}>
|
||||
<input
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
value={form.username}
|
||||
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>
|
||||
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>
|
||||
<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="groups"
|
||||
value=""
|
||||
name="jobTitle"
|
||||
value={form.jobTitle}
|
||||
onChange={handleChange}
|
||||
required
|
||||
className="add-user__select"
|
||||
>
|
||||
<option value="">Select Group</option>
|
||||
<option value="">Select Job Title</option>
|
||||
<option value="Admin">Admin</option>
|
||||
<option value="Reader">Reader</option>
|
||||
<option value="Manager">Manager</option>
|
||||
<option value="User">User</option>
|
||||
<option value="Manager">Manager</option>
|
||||
<option value="Supervisor">Supervisor</option>
|
||||
<option value="Director">Director</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 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>
|
||||
))}
|
||||
</div>
|
||||
</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>
|
||||
)}
|
||||
</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"
|
||||
<span
|
||||
className="phone-error-message"
|
||||
style={{
|
||||
visibility: phoneError ? "visible" : "hidden",
|
||||
color: "red",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
{phoneError}
|
||||
</span>
|
||||
|
||||
{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>
|
||||
<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 AddUserForm;
|
||||
export default AddUser;
|
||||
|
||||
63
app/features/UserRoles/NewUser/NewUser.scss
Normal file
63
app/features/UserRoles/NewUser/NewUser.scss
Normal file
@ -0,0 +1,63 @@
|
||||
.new-user__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
padding: 1rem 0;
|
||||
|
||||
.new-user__row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
|
||||
.new-user__label {
|
||||
font-weight: 600;
|
||||
color: #444;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.new-user__value {
|
||||
background: #f7f7f8;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.95rem;
|
||||
color: #222;
|
||||
word-break: break-word;
|
||||
|
||||
code {
|
||||
background: #272822;
|
||||
color: #f8f8f2;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
font-family: "Source Code Pro", monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Optional: Add a subtle hover highlight for readability */
|
||||
.new-user__row:hover .new-user__value {
|
||||
border-color: #bdbdbd;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* Responsive tweak for smaller screens */
|
||||
@media (max-width: 600px) {
|
||||
.new-user__content {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.new-user__row {
|
||||
.new-user__label {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.new-user__value {
|
||||
font-size: 0.9rem;
|
||||
padding: 0.6rem 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
app/features/UserRoles/NewUser/NewUser.tsx
Normal file
43
app/features/UserRoles/NewUser/NewUser.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import Modal from "@/app/components/Modal/Modal";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { clearAddedUser } from "@/app/redux/auth/authSlice";
|
||||
import { AppDispatch } from "@/app/redux/types";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { selectAddedUser } from "@/app/redux/auth/selectors";
|
||||
import { setShowNewUserModal } from "@/app/redux/ui/uiSlice";
|
||||
import "./NewUser.scss";
|
||||
|
||||
const NewUser: React.FC = () => {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const router = useRouter();
|
||||
const user = useSelector(selectAddedUser);
|
||||
|
||||
const handleClose = () => {
|
||||
dispatch(clearAddedUser());
|
||||
dispatch(setShowNewUserModal(false));
|
||||
// Refresh data after closing to update lists without racing rehydrate
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal open={true} onClose={handleClose} title="New user Details">
|
||||
<div className="new-user__content">
|
||||
<div className="new-user__row">
|
||||
<label className="new-user__label">Email</label>
|
||||
<div className="new-user__value">{user?.email}</div>
|
||||
</div>
|
||||
<div className="new-user__row">
|
||||
<label className="new-user__label">Temporary Password</label>
|
||||
<div className="new-user__value">
|
||||
<code>{user?.password}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewUser;
|
||||
@ -1,6 +1,6 @@
|
||||
import { RootState } from "@/app/redux/types";
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../redux/types";
|
||||
|
||||
interface MainContentProps {
|
||||
children: React.ReactNode;
|
||||
|
||||
@ -5,6 +5,7 @@ import ReduxProvider from "./ReduxProvider"; // moved into app/
|
||||
import { AuthBootstrap } from "./AuthBootstrap"; // moved into app/
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import "../styles/globals.scss";
|
||||
import Modals from "./modals";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Your App",
|
||||
@ -21,6 +22,7 @@ export default function RootLayout({
|
||||
<body>
|
||||
<ReduxProvider>
|
||||
{/* Bootstraps session validation + redirect if invalid */}
|
||||
<Modals />
|
||||
<AuthBootstrap />
|
||||
<ThemeRegistry>{children}</ThemeRegistry>
|
||||
<Toaster position="top-right" reverseOrder={false} />
|
||||
|
||||
14
app/modals.tsx
Normal file
14
app/modals.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import NewUser from "./features/UserRoles/NewUser/NewUser";
|
||||
import { useSelector } from "react-redux";
|
||||
// import { selectIsLoggedIn } from "./redux/auth/selectors";
|
||||
import { selectShowNewUserModal } from "./redux/ui/selectors";
|
||||
|
||||
const Modals: React.FC = () => {
|
||||
// const isLoggedIn = useSelector(selectIsLoggedIn);
|
||||
const showNewUserModal = useSelector(selectShowNewUserModal);
|
||||
|
||||
return <>{showNewUserModal && <NewUser />}</>;
|
||||
};
|
||||
export default Modals;
|
||||
@ -18,6 +18,7 @@ interface AuthState {
|
||||
user: IUserResponse | null;
|
||||
tokenInfo: TokenInfo | null;
|
||||
mustChangePassword: boolean;
|
||||
addedUser: IUserResponse | null;
|
||||
}
|
||||
|
||||
const initialState: AuthState = {
|
||||
@ -28,6 +29,7 @@ const initialState: AuthState = {
|
||||
user: null,
|
||||
tokenInfo: null,
|
||||
mustChangePassword: false,
|
||||
addedUser: null,
|
||||
};
|
||||
|
||||
// ---------------- Login ----------------
|
||||
@ -179,7 +181,7 @@ export const validateAuth = createAsyncThunk<
|
||||
|
||||
// ---------------- Add User ----------------
|
||||
export const addUser = createAsyncThunk<
|
||||
ThunkSuccess<{ user: IUserResponse }>,
|
||||
ThunkSuccess<{ user: IUserResponse; success: boolean }>,
|
||||
IEditUserForm,
|
||||
{ rejectValue: ThunkError; state: RootState }
|
||||
>("auth/addUser", async (userData, { rejectWithValue, getState }) => {
|
||||
@ -195,14 +197,17 @@ export const addUser = createAsyncThunk<
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
console.log("[DEBUG] [ADD-USER] [data]: ", data);
|
||||
|
||||
if (!res.ok) {
|
||||
return rejectWithValue(data.message || "Failed to create user");
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success,
|
||||
message: data.message || "User created successfully",
|
||||
user: data.user,
|
||||
};
|
||||
} as ThunkSuccess<{ user: IUserResponse; success: boolean }>;
|
||||
} catch (err: unknown) {
|
||||
return rejectWithValue(
|
||||
(err as Error).message || "Network error during user creation"
|
||||
@ -253,6 +258,9 @@ const authSlice = createSlice({
|
||||
clearAuthMessage: state => {
|
||||
state.authMessage = "";
|
||||
},
|
||||
clearAddedUser: state => {
|
||||
state.addedUser = null;
|
||||
},
|
||||
},
|
||||
extraReducers: builder => {
|
||||
builder
|
||||
@ -277,12 +285,11 @@ const authSlice = createSlice({
|
||||
})
|
||||
// Logout
|
||||
.addCase(logout.fulfilled, (state, action) => {
|
||||
state.status = "succeeded";
|
||||
state.isLoggedIn = false;
|
||||
state.authMessage = action.payload.message;
|
||||
state.user = null;
|
||||
state.tokenInfo = null;
|
||||
state.mustChangePassword = false;
|
||||
return {
|
||||
...initialState,
|
||||
status: "succeeded",
|
||||
authMessage: action.payload.message,
|
||||
};
|
||||
})
|
||||
.addCase(logout.rejected, (state, action) => {
|
||||
state.status = "failed";
|
||||
@ -291,12 +298,12 @@ const authSlice = createSlice({
|
||||
})
|
||||
// Auto Logout
|
||||
.addCase(autoLogout.fulfilled, (state, action) => {
|
||||
state.status = "succeeded";
|
||||
state.isLoggedIn = false;
|
||||
state.authMessage = action.payload.message;
|
||||
state.user = null;
|
||||
state.tokenInfo = null;
|
||||
state.mustChangePassword = false;
|
||||
return {
|
||||
...initialState,
|
||||
status: "succeeded",
|
||||
isLoggedIn: false,
|
||||
authMessage: action.payload.message,
|
||||
};
|
||||
})
|
||||
.addCase(autoLogout.rejected, (state, action) => {
|
||||
state.status = "failed";
|
||||
@ -336,15 +343,18 @@ const authSlice = createSlice({
|
||||
.addCase(addUser.pending, state => {
|
||||
state.status = "loading";
|
||||
state.authMessage = "Creating user...";
|
||||
state.addedUser = null;
|
||||
})
|
||||
.addCase(addUser.fulfilled, (state, action) => {
|
||||
state.status = "succeeded";
|
||||
state.authMessage = action.payload.message;
|
||||
state.addedUser = action.payload.user;
|
||||
})
|
||||
.addCase(addUser.rejected, (state, action) => {
|
||||
state.status = "failed";
|
||||
state.error = action.payload as string;
|
||||
state.authMessage = action.payload as string;
|
||||
state.addedUser = null;
|
||||
})
|
||||
// Update User Details
|
||||
.addCase(updateUserDetails.pending, state => {
|
||||
@ -364,5 +374,6 @@ const authSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { setAuthMessage, clearAuthMessage } = authSlice.actions;
|
||||
export const { setAuthMessage, clearAuthMessage, clearAddedUser } =
|
||||
authSlice.actions;
|
||||
export default authSlice.reducer;
|
||||
|
||||
@ -12,3 +12,4 @@ export const selectTimeUntilExpiration = (state: RootState) =>
|
||||
state.auth?.tokenInfo?.timeUntilExpiration || 0;
|
||||
export const selectExpiresInHours = (state: RootState) =>
|
||||
state.auth?.tokenInfo?.expiresInHours || 0;
|
||||
export const selectAddedUser = (state: RootState) => state.auth?.addedUser;
|
||||
|
||||
@ -11,6 +11,7 @@ import authReducer from "./auth/authSlice";
|
||||
import uiReducer from "./ui/uiSlice";
|
||||
import userEpics from "./user/epic";
|
||||
import authEpics from "./auth/epic";
|
||||
import uiEpics from "./ui/epic";
|
||||
|
||||
type PersistedAuth = { user: unknown | null };
|
||||
|
||||
@ -40,7 +41,7 @@ const rootReducer = combineReducers({
|
||||
ui: uiReducer,
|
||||
});
|
||||
|
||||
const rootEpic = combineEpics(...userEpics, ...authEpics);
|
||||
const rootEpic = combineEpics(...userEpics, ...authEpics, ...uiEpics);
|
||||
|
||||
const persistedReducer = persistReducer(persistConfig, rootReducer as Reducer);
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ export type ThunkSuccess<T extends object = object> = { message: string } & T;
|
||||
export type ThunkError = string;
|
||||
|
||||
export interface IUserResponse {
|
||||
success: boolean;
|
||||
token: string;
|
||||
id: string;
|
||||
name: string | null;
|
||||
@ -19,6 +20,7 @@ export interface IUserResponse {
|
||||
phone?: string | null;
|
||||
jobTitle?: string | null;
|
||||
enabled?: boolean;
|
||||
password?: string;
|
||||
authorities?: string[];
|
||||
allowedMerchantIds?: number[];
|
||||
created?: string;
|
||||
@ -40,5 +42,5 @@ export interface ILoginResponse {
|
||||
user: IUserResponse;
|
||||
success: boolean;
|
||||
message: string;
|
||||
must_change_password: boolean;
|
||||
must_change_password?: boolean;
|
||||
}
|
||||
|
||||
16
app/redux/ui/epic.ts
Normal file
16
app/redux/ui/epic.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Epic } from "redux-observable";
|
||||
import { filter, map } from "rxjs/operators";
|
||||
import { addUser } from "../auth/authSlice";
|
||||
import { setShowNewUserModal } from "./uiSlice";
|
||||
|
||||
export const logoutRedirectEpic: Epic = action$ =>
|
||||
action$.pipe(
|
||||
filter(addUser.fulfilled.match),
|
||||
map(() => {
|
||||
return setShowNewUserModal(true);
|
||||
})
|
||||
);
|
||||
|
||||
const authEpics = [logoutRedirectEpic];
|
||||
|
||||
export default authEpics;
|
||||
4
app/redux/ui/selectors.ts
Normal file
4
app/redux/ui/selectors.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { RootState } from "../types";
|
||||
|
||||
export const selectShowNewUserModal = (state: RootState) =>
|
||||
state.ui.showNewUserModal;
|
||||
@ -2,10 +2,12 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface UIState {
|
||||
sidebarOpen: boolean;
|
||||
showNewUserModal: boolean;
|
||||
}
|
||||
|
||||
const initialState: UIState = {
|
||||
sidebarOpen: true,
|
||||
showNewUserModal: false,
|
||||
};
|
||||
|
||||
const uiSlice = createSlice({
|
||||
@ -18,8 +20,12 @@ const uiSlice = createSlice({
|
||||
setSidebarOpen: (state, action: PayloadAction<boolean>) => {
|
||||
state.sidebarOpen = action.payload;
|
||||
},
|
||||
setShowNewUserModal: (state, action: PayloadAction<boolean>) => {
|
||||
state.showNewUserModal = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { toggleSidebar, setSidebarOpen } = uiSlice.actions;
|
||||
export const { toggleSidebar, setSidebarOpen, setShowNewUserModal } =
|
||||
uiSlice.actions;
|
||||
export default uiSlice.reducer;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user