Added Edit Uder in Admin

This commit is contained in:
Mitchell Magro 2025-07-09 14:41:51 +02:00
parent 2864bf8cdc
commit fc4be718f7
21 changed files with 521 additions and 337 deletions

View File

@ -0,0 +1,66 @@
// app/api/user/route.ts
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json([
{
merchantId: 100987998,
id: "bc6a8a55-13bc-4538-8255-cd0cec3bb4e9",
mame: "Jacob",
username: "lspaddy",
firstName: "Paddy",
lastName: "Man",
email: "patrick@omegasys.eu",
phone: "",
jobTitle: "",
enabled: true,
authorities: [
"ROLE_IIN",
"ROLE_FIRST_APPROVER",
"ROLE_RULES_ADMIN",
"ROLE_TRANSACTION_VIEWER",
"ROLE_IIN_ADMIN",
"ROLE_USER_PSP_ACCOUNT",
],
allowedMerchantIds: [100987998],
created: "2025-05-04T15:32:48.432Z",
disabledBy: null,
disabledDate: null,
disabledReason: null,
incidentNotes: false,
lastLogin: "",
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
marketingNewsletter: false,
releaseNotes: false,
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
twoFactorCondition: "required",
twoFactorCredentials: [],
},
{
merchantId: 100987998,
mame: "Jacob",
id: "382eed15-1e21-41fa-b1f3-0c1adb3af714",
username: "lsterence",
firstName: "Terence",
lastName: "User",
email: "terence@omegasys.eu",
phone: "",
jobTitle: "",
enabled: true,
authorities: ["ROLE_IIN", "ROLE_FIRST_APPROVER", "ROLE_RULES_ADMIN"],
allowedMerchantIds: [100987998],
created: "2025-05-04T15:32:48.432Z",
disabledBy: null,
disabledDate: null,
disabledReason: null,
incidentNotes: false,
lastLogin: "",
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
marketingNewsletter: false,
releaseNotes: false,
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
twoFactorCondition: "required",
twoFactorCredentials: [],
},
]);
}

View File

@ -1,23 +1,23 @@
import { transactionDummyData } from '@/app/features/Pages/transactions/mockData'; import { transactionDummyData } from "@/app/components/test/test2";
import { NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const state = searchParams.get('state'); const state = searchParams.get("state");
const user = searchParams.get('user'); const user = searchParams.get("user");
let filteredTransactions = [...transactionDummyData]; let filteredTransactions = [...transactionDummyData];
if (user) { if (user) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
tx => tx.user.toString() === user (tx) => tx.user.toString() === user
); );
} }
if (state) { if (state) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
tx => tx.state.toLowerCase() === state.toLowerCase() (tx) => tx.state.toLowerCase() === state.toLowerCase()
); );
} }

View File

@ -1,28 +1,62 @@
// app/transactions/page.tsx // app/transactions/page.tsx
'use client'; "use client";
import { useState } from 'react'; import { useState } from "react";
// mocks/transactionData.ts
export const transactionDummyData = [
{
id: 1,
merchandId: 100987998,
transactionID: 1049131973,
user: 1,
created: "2025-06-18 10:10:30",
state: "FAILED",
statusDescription: "ERR_ABOVE_LIMIT",
pspStatusCode: 100501,
},
{
id: 2,
merchandId: 100987998,
transactionID: 1049131973,
user: 2,
created: "2025-06-18 10:10:30",
state: "FAILED",
statusDescription: "ERR_ABOVE_LIMIT",
pspStatusCode: 100501,
},
{
id: 3,
merchandId: 100987998,
transactionID: 1049131973,
user: 3,
created: "2025-06-18 10:10:30",
state: "FAILED",
statusDescription: "ERR_ABOVE_LIMIT",
pspStatusCode: 100501,
},
];
export default function TransactionsPage() { export default function TransactionsPage() {
const [userId, setUserId] = useState(''); const [userId, setUserId] = useState("");
const [state, setState] = useState(''); const [state, setState] = useState("");
const [statusCode, setStatusCode] = useState(''); const [statusCode, setStatusCode] = useState("");
const [transactions, setTransactions] = useState<any[]>([]); const [transactions, setTransactions] = useState<any[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const fetchTransactions = async () => { const fetchTransactions = async () => {
setLoading(true); setLoading(true);
try { try {
const url = new URL('https://api.example.com/transactions'); const url = new URL("https://api.example.com/transactions");
if (userId) url.searchParams.append('userId', userId); if (userId) url.searchParams.append("userId", userId);
if (state) url.searchParams.append('state', state); if (state) url.searchParams.append("state", state);
if (statusCode) url.searchParams.append('statusCode', statusCode); if (statusCode) url.searchParams.append("statusCode", statusCode);
const response = await fetch(url.toString()); const response = await fetch(url.toString());
const data = await response.json(); const data = await response.json();
setTransactions(data.transactions); setTransactions(data.transactions);
} catch (error) { } catch (error) {
console.error('Error fetching transactions:', error); console.error("Error fetching transactions:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -72,7 +106,7 @@ export default function TransactionsPage() {
disabled={loading} disabled={loading}
className="bg-blue-500 text-white px-4 py-2 rounded text-sm" className="bg-blue-500 text-white px-4 py-2 rounded text-sm"
> >
{loading ? 'Loading...' : 'Search'} {loading ? "Loading..." : "Search"}
</button> </button>
</div> </div>
</div> </div>
@ -104,86 +138,49 @@ export default function TransactionsPage() {
</div> </div>
) : ( ) : (
<div className="text-center py-4 text-sm"> <div className="text-center py-4 text-sm">
{loading ? 'Loading transactions...' : 'No transactions found'} {loading ? "Loading transactions..." : "No transactions found"}
</div> </div>
)} )}
</div> </div>
); );
} }
// mocks/handlers.ts // mocks/handlers.ts
import { http, HttpResponse } from 'msw'; import { http, HttpResponse } from "msw";
import { transactionDummyData } from './transactionData';
export const handlers = [ export const handlers = [
http.get('https://api.example.com/transactions', ({ request }) => { http.get("https://api.example.com/transactions", ({ request }) => {
const url = new URL(request.url); const url = new URL(request.url);
// Get query parameters // Get query parameters
const userId = url.searchParams.get('userId'); const userId = url.searchParams.get("userId");
const state = url.searchParams.get('state'); const state = url.searchParams.get("state");
const statusCode = url.searchParams.get('statusCode'); const statusCode = url.searchParams.get("statusCode");
// Filter transactions based on query parameters // Filter transactions based on query parameters
let filteredTransactions = [...transactionDummyData]; let filteredTransactions = [...transactionDummyData];
if (userId) { if (userId) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
tx => tx.user.toString() === userId (tx) => tx.user.toString() === userId
); );
} }
if (state) { if (state) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
tx => tx.state.toLowerCase() === state.toLowerCase() (tx) => tx.state.toLowerCase() === state.toLowerCase()
); );
} }
if (statusCode) { if (statusCode) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
tx => tx.pspStatusCode.toString() === statusCode (tx) => tx.pspStatusCode.toString() === statusCode
); );
} }
return HttpResponse.json({ return HttpResponse.json({
transactions: filteredTransactions, transactions: filteredTransactions,
count: filteredTransactions.length count: filteredTransactions.length,
}); });
}), }),
]; ];
// mocks/transactionData.ts
export const transactionDummyData = [
{
id: 1,
merchandId: 100987998,
transactionID: 1049131973,
user: 1,
created: "2025-06-18 10:10:30",
state: "FAILED",
statusDescription: "ERR_ABOVE_LIMIT",
pspStatusCode: 100501,
},
{
id: 2,
merchandId: 100987998,
transactionID: 1049131973,
user: 2,
created: "2025-06-18 10:10:30",
state: "FAILED",
statusDescription: "ERR_ABOVE_LIMIT",
pspStatusCode: 100501,
},
{
id: 3,
merchandId: 100987998,
transactionID: 1049131973,
user: 3,
created: "2025-06-18 10:10:30",
state: "FAILED",
statusDescription: "ERR_ABOVE_LIMIT",
pspStatusCode: 100501,
}
];

View File

@ -1,12 +1,15 @@
// This ensures this component is rendered only on the client side
"use client";
import Users from "@/app/features/Pages/Admin/Users/users"; import Users from "@/app/features/Pages/Admin/Users/users";
export default function BackOfficeUsersPage() { export default async function BackOfficeUsersPage() {
const res = await fetch("http://localhost:3000/api/dashboard/admin/users", {
cache: "no-store", // 👈 disables caching for SSR freshness
});
const users = await res.json();
console.log("[USERS]", users);
return ( return (
<div> <div>
<Users /> <Users users={users} />
</div> </div>
); );
} }

View File

@ -1,92 +1,17 @@
"use client"; "use client";
import React, { useEffect, useState } from "react"; import React from "react";
import { Card, CardContent, Typography, Chip, Stack } from "@mui/material"; import { Card, CardContent, Typography, Stack } from "@mui/material";
import { IUser } from "./interfaces"; import { IUser } from "./interfaces";
import UserRoleCard from "@/app/features/UserRoles/userRoleCard"; import UserRoleCard from "@/app/features/UserRoles/UserRoleCard";
const Users = () => { interface UsersProps {
const [data, setData] = useState([ users: IUser[];
{
merchantId: 100987998,
id: "bc6a8a55-13bc-4538-8255-cd0cec3bb4e9",
mame: "Jacob",
username: "lspaddy",
firstName: "Paddy",
lastName: "Man",
email: "patrick@omegasys.eu",
phone: "",
jobTitle: "",
enabled: true,
authorities: [
"ROLE_IIN",
"ROLE_FIRST_APPROVER",
"ROLE_RULES_ADMIN",
"ROLE_TRANSACTION_VIEWER",
"ROLE_IIN_ADMIN",
"ROLE_USER_PSP_ACCOUNT",
],
allowedMerchantIds: [100987998],
created: "2025-05-04T15:32:48.432Z",
disabledBy: null,
disabledDate: null,
disabledReason: null,
incidentNotes: false,
lastLogin: "",
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
marketingNewsletter: false,
releaseNotes: false,
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
twoFactorCondition: "required",
twoFactorCredentials: [],
},
{
merchantId: 100987998,
mame: "Jacob",
id: "382eed15-1e21-41fa-b1f3-0c1adb3af714",
username: "lsterence",
firstName: "Terence",
lastName: "User",
email: "terence@omegasys.eu",
phone: "",
jobTitle: "",
enabled: true,
authorities: ["ROLE_IIN", "ROLE_FIRST_APPROVER", "ROLE_RULES_ADMIN"],
allowedMerchantIds: [100987998],
created: "2025-05-04T15:32:48.432Z",
disabledBy: null,
disabledDate: null,
disabledReason: null,
incidentNotes: false,
lastLogin: "",
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
marketingNewsletter: false,
releaseNotes: false,
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
twoFactorCondition: "required",
twoFactorCredentials: [],
},
// Add more users if needed
]);
useEffect(() => {
// Only run MSW in the browser environment
if (typeof window !== "undefined") {
const fetchData = async () => {
const response = await fetch(
"https://test-bo.paymentiq.io/paymentiq/backoffice/api/v2/users/?includeSubMids=false&size=20&page=0&merchantId=100987998"
); // This would be intercepted by MSW in the browser
const data = await response.json();
console.log("[DATA]", data);
// setData(data);
};
fetchData();
} }
}, []);
const Users: React.FC<UsersProps> = ({ users }) => {
return ( return (
<div> <div>
{data.map((user: IUser) => ( {users.map((user: IUser) => (
<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>
@ -103,7 +28,7 @@ const Users = () => {
isAdmin={true} isAdmin={true}
lastLogin="small" lastLogin="small"
roles={user.authorities} roles={user.authorities}
// merchants={Numberuser.allowedMerchantIds} merchants={[]} // merchants={Numberuser.allowedMerchantIds}
/> />
{/* Add more chips or UI elements for other data */} {/* Add more chips or UI elements for other data */}
</Stack> </Stack>
@ -113,19 +38,4 @@ const Users = () => {
</div> </div>
); );
}; };
// Fetch data server-side using getServerSideProps
// export async function getServerSideProps() {
// // Replace this with your actual API call
// const res = await fetch("https://api.example.com/users");
// const data = await res.json();
// // Return the fetched data as props
// return {
// props: {
// result: data, // Assuming data is an array of users
// },
// };
// }
export default Users; export default Users;

View File

@ -1,3 +1,4 @@
"use client";
import { Box } from "@mui/material"; import { Box } from "@mui/material";
import { GeneralHealthCard } from "../../GeneralHealthCard/GeneralHealthCard"; import { GeneralHealthCard } from "../../GeneralHealthCard/GeneralHealthCard";
import { TransactionsWaitingApproval } from "../../TransactionsWaitingApproval/TransactionsWaitingApproval"; import { TransactionsWaitingApproval } from "../../TransactionsWaitingApproval/TransactionsWaitingApproval";
@ -6,53 +7,8 @@ import { Documentation } from "../../Documentation/Documentation";
import { AccountIQ } from "../../AccountIQ/AccountIQ"; import { AccountIQ } from "../../AccountIQ/AccountIQ";
import { WhatsNew } from "../../WhatsNew/WhatsNew"; import { WhatsNew } from "../../WhatsNew/WhatsNew";
import { TransactionsOverView } from "../../TransactionsOverview/TransactionsOverview"; import { TransactionsOverView } from "../../TransactionsOverview/TransactionsOverview";
import { useEffect } from "react";
export const DashboardHomePage = () => { export const DashboardHomePage = () => {
// useEffect to fetch data when the component mounts
useEffect(() => {
// Construct the URL with query parameters
const url =
"https://test-bo.paymentiq.io/paymentiq/backoffice/api/v2/metrics/txsummary?merchantId=100987998&fromDate=2025-06-29+19%3A10&toDate=2025-07-01+19%3A09";
// Perform the fetch request inside useEffect
const fetchData = async () => {
try {
// Start loading
// Make the API request
const response = await fetch(url, {
method: "GET", // You can change this to 'POST' if necessary
headers: {
"Content-Type": "application/json",
// Add any other headers you need here
},
});
// Check if the response is OK (status code 200-299)
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`);
}
// Parse the JSON response
const data = await response.json();
console.log("[DATA]", data);
// Update the state with the fetched data
// setTransactions(data.result || []);
} catch (err) {
// Handle any errors that occurred during the fetch
// setError("Failed to load data, please try again.");
console.error("Fetch error:", err);
} finally {
// Set loading to false when the request is complete
// setLoading(false);
}
};
// Call the fetch function
fetchData();
}, []); // Empty dependency
return ( return (
<> <>
<Box sx={{ p: 2 }}> <Box sx={{ p: 2 }}>

View File

@ -28,7 +28,6 @@ import SearchFilters from "@/app/components/searchFilter/SearchFilters";
const paginationModel = { page: 0, pageSize: 50 }; const paginationModel = { page: 0, pageSize: 50 };
export default function TransactionTable() { export default function TransactionTable() {
const [form, setForm] = useState({ const [form, setForm] = useState({
keyword: "", keyword: "",
transactionID: "", transactionID: "",
@ -80,7 +79,9 @@ export default function TransactionTable() {
const [onlyCurrentTable, setOnlyCurrentTable] = useState(false); const [onlyCurrentTable, setOnlyCurrentTable] = useState(false);
const handleExport = () => { const handleExport = () => {
const exportRows = onlyCurrentTable ? transactions.slice(0, 5) : transactions; const exportRows = onlyCurrentTable
? transactions.slice(0, 5)
: transactions;
const exportData = [ const exportData = [
columns.map((col) => col.headerName), columns.map((col) => col.headerName),
...exportRows.map((row) => columns.map((col) => row[col.field] ?? "")), ...exportRows.map((row) => columns.map((col) => row[col.field] ?? "")),
@ -103,7 +104,6 @@ export default function TransactionTable() {
setOpen(false); setOpen(false);
}; };
const [transactions, setTransactions] = useState<any[]>([]); const [transactions, setTransactions] = useState<any[]>([]);
const fetchTransactions = async () => { const fetchTransactions = async () => {
@ -113,33 +113,27 @@ export default function TransactionTable() {
const data = await res.json(); const data = await res.json();
setTransactions(data); setTransactions(data);
} catch (error) { } catch (error) {
console.error('Error fetching transactions:', error); console.error("Error fetching transactions:", error);
} }
}; };
useEffect(() => { useEffect(() => {
fetchTransactions(); fetchTransactions();
}, [form]); }, [form]);
const handleDeleteFilter = (key) => { const handleDeleteFilter = (key) => {
setForm((prev) => ({ ...prev, [key]: '' })); setForm((prev) => ({ ...prev, [key]: "" }));
}; };
const handleClearAll = () => { const handleClearAll = () => {
resetForm() resetForm();
fetchTransactions() fetchTransactions();
}; };
const handleClickField = (field: string, value: any) => { const handleClickField = (field: string, value: any) => {
setForm((prev) => ({ ...prev, [field]: value })); setForm((prev) => ({ ...prev, [field]: value }));
}; };
return ( return (
<StyledPaper> <StyledPaper>
<Stack <Stack
@ -157,7 +151,11 @@ export default function TransactionTable() {
sx={{ width: 300 }} sx={{ width: 300 }}
/> />
<AdvancedSearch form={form} resetForm={resetForm} setForm={setForm} /> <AdvancedSearch form={form} resetForm={resetForm} setForm={setForm} />
<SearchFilters filters={form} onDeleteFilter={handleDeleteFilter} onClearAll={handleClearAll} /> <SearchFilters
filters={form}
onDeleteFilter={handleDeleteFilter}
onClearAll={handleClearAll}
/>
{/* <RightTemporaryDrawer /> */} {/* <RightTemporaryDrawer /> */}
{/* <SearchFilterForm /> */} {/* <SearchFilterForm /> */}
<Button <Button
@ -174,15 +172,14 @@ export default function TransactionTable() {
columns={columns} columns={columns}
initialState={{ pagination: { paginationModel } }} initialState={{ pagination: { paginationModel } }}
pageSizeOptions={[50, 100]} pageSizeOptions={[50, 100]}
sx={{ border: 0, cursor: 'pointer' }} sx={{ border: 0, cursor: "pointer" }}
onCellClick={(params, event) => { onCellClick={(params, event) => {
// Check if the click is on a specific column // Check if the click is on a specific column
// Do something when this specific column is clicked // Do something when this specific column is clicked
handleClickField(params.field, params.value) handleClickField(params.field, params.value);
console.log('Clicked cell value:', params.value); // The cell's value console.log("Clicked cell value:", params.value); // The cell's value
console.log('Column field:', params.field); // The column's field name (from your columns definition) console.log("Column field:", params.field); // The column's field name (from your columns definition)
console.log('Column header:', params.colDef.headerName); // The column's display name // Your custom logic here console.log("Column header:", params.colDef.headerName); // The column's display name // Your custom logic here
}} }}
/> />

View File

@ -0,0 +1,46 @@
.edit-user {
margin-top: 30px;
display: flex;
flex-wrap: wrap;
gap: 16px;
input {
flex: 1 1 20%;
min-width: 150px;
box-sizing: border-box;
padding: 8px;
font-size: 1rem;
border-radius: 4px;
border: 1px solid #ccc;
outline: none;
transition: border-color 0.3s ease;
&:focus {
border-color: #0070f3;
}
}
&__button-container {
flex-basis: 100%;
width: 100%;
button {
flex-basis: 100%;
margin-top: 16px;
padding: 10px 0;
font-size: 1rem;
border-radius: 4px;
width: 100px;
cursor: pointer;
}
button:first-child {
color: var(--button-primary);
border-color: var(--button-primary);
}
button:last-child {
color: var(--button-secondary);
border-color: var(--button-secondary);
margin-left: 8;
}
}
}

View File

@ -0,0 +1,46 @@
.edit-user {
margin-top: 30px;
display: flex;
flex-wrap: wrap;
gap: 16px;
input {
flex: 1 1 20%;
min-width: 150px;
box-sizing: border-box;
padding: 8px;
font-size: 1rem;
border-radius: 4px;
border: 1px solid #ccc;
outline: none;
transition: border-color 0.3s ease;
&:focus {
border-color: #0070f3;
}
}
&__button-container {
flex-basis: 100%;
width: 100%;
button {
flex-basis: 100%;
margin-top: 16px;
padding: 10px 0;
font-size: 1rem;
border-radius: 4px;
width: 100px;
cursor: pointer;
}
button:first-child {
color: var(--button-primary);
border-color: var(--button-primary);
}
button:last-child {
color: var(--button-secondary);
border-color: var(--button-secondary);
margin-left: 8;
}
}
}

View File

@ -0,0 +1,107 @@
import React from "react";
import "./editUser.scss";
// Union type for form field names
export type EditUserField =
| "firstName"
| "lastName"
| "email"
| "role"
| "phone";
interface IEditUserForm {
firstName: string;
lastName: string;
email: string;
role: string;
phone: string;
}
const EditUser = () => {
const [form, setForm] = React.useState<IEditUserForm>({
firstName: "",
lastName: "",
email: "",
role: "",
phone: "",
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const name = e.target.name as EditUserField;
const value = e.target.value;
if (name === "phone") {
const filtered = value.replace(/[^0-9+\-\s()]/g, "");
setForm((prev) => ({
...prev,
phone: filtered,
}));
} else {
setForm((prev) => ({
...prev,
[name]: value,
}));
}
};
const handleResetForm = () => {
setForm({
firstName: "",
lastName: "",
email: "",
role: "",
phone: "",
});
};
return (
<form className="edit-user">
<input
type="text"
placeholder="First Name"
name="firstName"
value={form.firstName}
onChange={handleChange}
/>
<input
type="text"
placeholder="Last Name"
name="lastName"
value={form.lastName}
onChange={handleChange}
/>
<input
type="email"
placeholder="Email"
name="email"
value={form.email}
onChange={handleChange}
/>
<input
type="text"
placeholder="Role"
name="role"
value={form.role}
onChange={handleChange}
/>
<input
type="tel"
placeholder="Phone"
name="phone"
value={form.phone}
maxLength={15}
pattern="[0-9+\-\s()]*"
onChange={handleChange}
inputMode="tel"
autoComplete="tel"
/>
<div className="edit-user__button-container">
<button type="submit">Save</button>
<button type="button" onClick={handleResetForm}>
Clear
</button>
</div>
</form>
);
};
export default EditUser;

View File

@ -0,0 +1,6 @@
.edit-user {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}

View File

@ -18,6 +18,8 @@ import {
AdminPanelSettings, AdminPanelSettings,
History, History,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { useState } from "react";
import EditUser from "./EditUser/EditUser";
interface Props { interface Props {
username: string; username: string;
@ -40,12 +42,18 @@ export default function UserRoleCard({
roles, roles,
extraRolesCount, extraRolesCount,
}: Props) { }: Props) {
const [isEditing, setIsEditing] = useState(false);
const handleEditClick = () => {
setIsEditing(!isEditing);
};
return ( return (
<Card sx={{ mb: 2, minWidth: "100%" }}> <Card sx={{ mb: 2, minWidth: "100%" }}>
<CardContent> <CardContent>
{/* Header */} {/* Header */}
<Stack direction="row" alignItems="center" spacing={2}> <Stack direction="row" alignItems="center" spacing={2}>
<Avatar>{username.slice(0, 2).toUpperCase()}</Avatar> <Avatar>{username?.slice(0, 2).toUpperCase()}</Avatar>
<Box flexGrow={1}> <Box flexGrow={1}>
<Typography fontWeight="bold">{username}</Typography> <Typography fontWeight="bold">{username}</Typography>
<Typography variant="body2">{name}</Typography> <Typography variant="body2">{name}</Typography>
@ -58,7 +66,7 @@ export default function UserRoleCard({
<History /> <History />
</IconButton> </IconButton>
<Tooltip title="Edit"> <Tooltip title="Edit">
<IconButton> <IconButton onClick={handleEditClick}>
<Edit /> <Edit />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@ -105,13 +113,13 @@ export default function UserRoleCard({
{extraRolesCount && <Chip label={`+${extraRolesCount}`} />} {extraRolesCount && <Chip label={`+${extraRolesCount}`} />}
</Stack> </Stack>
</Box> </Box>
{isEditing && <EditUser />}
{/* Footer */} {/* Footer */}
<Box mt={2}> {/* <Box mt={2}>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary">
{lastLogin} {lastLogin}
</Typography> </Typography>
</Box> </Box> */}
</CardContent> </CardContent>
</Card> </Card>
); );

View File

@ -1,11 +1,12 @@
'use client'; "use client";
import { useEffect } from 'react'; import { useEffect } from "react";
export function MSWProvider({ children }: { children: React.ReactNode }) { export function MSWProvider({ children }: { children: React.ReactNode }) {
useEffect(() => { useEffect(() => {
if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') { if (process.env.NEXT_PUBLIC_API_MOCKING === "enabled") {
const { worker } = require('../../mock/browser.ts'); import("../../mock/browser").then(({ worker }) => {
worker.start(); // worker.start();
});
} }
}, []); }, []);

View File

@ -1,39 +1,43 @@
import { createTheme } from '@mui/material/styles'; import { createTheme } from "@mui/material/styles";
const palette = { const palette = {
primary: { primary: {
main: '#1976d2', main: "#1976d2",
}, },
secondary: { secondary: {
main: '#d32f2f', main: "#d32f2f",
}, },
background: { background: {
default: '#fafafa', default: "#fafafa",
paper: '#ffffff', paper: "#ffffff",
primary: 'rgb(69, 190, 171)', primary: "rgb(69, 190, 171)",
}, },
text: { text: {
primary: '#000000', primary: "#000000",
secondary: '#555555', secondary: "#555555",
tertiary: '#fff', tertiary: "#fff",
},
button: {
primary: "#0070f3",
secondary: "##FF00FF",
}, },
action: { action: {
hover: 'rgba(0, 0, 0, 0.08)', hover: "rgba(0, 0, 0, 0.08)",
}, },
}; };
const typography = { const typography = {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
h1: { h1: {
fontSize: '3rem', fontSize: "3rem",
fontWeight: 700, fontWeight: 700,
}, },
h2: { h2: {
fontSize: '2.5rem', fontSize: "2.5rem",
fontWeight: 700, fontWeight: 700,
}, },
body1: { body1: {
fontSize: '1rem', fontSize: "1rem",
fontWeight: 400, fontWeight: 400,
}, },
}; };
@ -41,8 +45,8 @@ const typography = {
// Create the theme based on the light or dark mode preference // Create the theme based on the light or dark mode preference
const theme = createTheme({ const theme = createTheme({
palette: { palette: {
mode: 'light', // Change this to 'dark' for dark mode mode: "light", // Change this to 'dark' for dark mode
...palette ...palette,
}, },
// typography, // typography,
breakpoints: { breakpoints: {

View File

@ -1,9 +1,4 @@
// // mocks/browser.ts import { setupWorker } from "msw/browser"; import { setupWorker } from "msw/browser";
// import { handlers } from "./handlers"; import { handlers } from "./handlers";
//
// export const worker = setupWorker(...handlers);
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers); export const worker = setupWorker(...handlers);

View File

@ -0,0 +1,61 @@
export const users = [
{
merchantId: 100987998,
id: "bc6a8a55-13bc-4538-8255-cd0cec3bb4e9",
mame: "Jacob",
username: "lspaddy",
firstName: "Paddy",
lastName: "Man",
email: "patrick@omegasys.eu",
phone: "",
jobTitle: "",
enabled: true,
authorities: [
"ROLE_IIN",
"ROLE_FIRST_APPROVER",
"ROLE_RULES_ADMIN",
"ROLE_TRANSACTION_VIEWER",
"ROLE_IIN_ADMIN",
"ROLE_USER_PSP_ACCOUNT",
],
allowedMerchantIds: [100987998],
created: "2025-05-04T15:32:48.432Z",
disabledBy: null,
disabledDate: null,
disabledReason: null,
incidentNotes: false,
lastLogin: "",
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
marketingNewsletter: false,
releaseNotes: false,
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
twoFactorCondition: "required",
twoFactorCredentials: [],
},
{
merchantId: 100987998,
mame: "Jacob",
id: "382eed15-1e21-41fa-b1f3-0c1adb3af714",
username: "lsterence",
firstName: "Terence",
lastName: "User",
email: "terence@omegasys.eu",
phone: "",
jobTitle: "",
enabled: true,
authorities: ["ROLE_IIN", "ROLE_FIRST_APPROVER", "ROLE_RULES_ADMIN"],
allowedMerchantIds: [100987998],
created: "2025-05-04T15:32:48.432Z",
disabledBy: null,
disabledDate: null,
disabledReason: null,
incidentNotes: false,
lastLogin: "",
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
marketingNewsletter: false,
releaseNotes: false,
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
twoFactorCondition: "required",
twoFactorCredentials: [],
},
];

View File

@ -1,72 +1,49 @@
// import { http, HttpResponse } from "msw"; import { http, HttpResponse } from "msw";
//
// export const handlers = [
// http.get(
// "https://test-bo.paymentiq.io/paymentiq/backoffice/api/v2/metrics/txsummary",
// (req, _res, _ctx) => {
// const merchantId = req.url.searchParams.get("merchantId");
// const fromDate = req.url.searchParams.get("fromDate");
// const toDate = req.url.searchParams.get("toDate");
//
// console.log(merchantId, fromDate, toDate);
//
// return HttpResponse.json({
// result: {
// txCount: { total: 0, successful: 0 },
// amount: { value: "0", currency: "EUR" },
// },
// });
// }
// ),
// ];
import { transactionDummyData } from '@/app/features/Pages/transactions/mockData';
import { http, HttpResponse } from 'msw';
export const handlers = [ export const handlers = [
// Simple GET endpoint // Simple GET endpoint
http.get('https://api.example.com/user', () => { http.get("https://api.example.com/user", () => {
return HttpResponse.json({ return HttpResponse.json([
id: 'usr_123', {
name: 'John Doe', id: "usr_123",
email: 'john@example.com' name: "John Doe",
}); email: "john@example.com",
},
]);
}), }),
// POST endpoint with request validation // POST endpoint with request validation
http.post('https://api.example.com/login', async ({ request }) => { http.post("https://api.example.com/login", async ({ request }) => {
const { username, password } = await request.json() as { username: string; password: string }; const { username, password } = (await request.json()) as {
username: string;
password: string;
};
if (username === 'admin' && password === 'password123') { if (username === "admin" && password === "password123") {
return HttpResponse.json({ return HttpResponse.json({
token: 'mock-jwt-token', token: "mock-jwt-token",
user: { id: 'usr_123', name: 'Admin User' } user: { id: "usr_123", name: "Admin User" },
}); });
} }
return HttpResponse.json( return HttpResponse.json({ error: "Invalid credentials" }, { status: 401 });
{ error: 'Invalid credentials' },
{ status: 401 }
);
}), }),
// Example with query parameters // Example with query parameters
http.get('https://api.example.com/products', ({ request }) => { http.get("https://api.example.com/products", ({ request }) => {
// Parse the URL to access query parameters // Parse the URL to access query parameters
const url = new URL(request.url); const url = new URL(request.url);
// Get query parameters // Get query parameters
const category = url.searchParams.get('category'); const category = url.searchParams.get("category");
const sort = url.searchParams.get('sort') || 'price'; const sort = url.searchParams.get("sort") || "price";
const page = url.searchParams.get('page') || '1'; const page = url.searchParams.get("page") || "1";
const limit = url.searchParams.get('limit') || '10'; const limit = url.searchParams.get("limit") || "10";
// Validate parameters // Validate parameters
if (limit && parseInt(limit) > 100) { if (limit && parseInt(limit) > 100) {
return HttpResponse.json( return HttpResponse.json(
{ error: 'Limit cannot exceed 100' }, { error: "Limit cannot exceed 100" },
{ status: 400 } { status: 400 }
); );
} }
@ -74,15 +51,15 @@ export const handlers = [
// Generate mock response based on parameters // Generate mock response based on parameters
const mockProducts = Array.from({ length: parseInt(limit) }, (_, i) => ({ const mockProducts = Array.from({ length: parseInt(limit) }, (_, i) => ({
id: i + 1, id: i + 1,
name: `Product ${i + 1}${category ? ` in ${category}` : ''}`, name: `Product ${i + 1}${category ? ` in ${category}` : ""}`,
price: Math.floor(Math.random() * 100), price: Math.floor(Math.random() * 100),
category: category || 'general', category: category || "general",
})); }));
// Sort products if sort parameter provided // Sort products if sort parameter provided
if (sort === 'price') { if (sort === "price") {
mockProducts.sort((a, b) => a.price - b.price); mockProducts.sort((a, b) => a.price - b.price);
} else if (sort === 'name') { } else if (sort === "name") {
mockProducts.sort((a, b) => a.name.localeCompare(b.name)); mockProducts.sort((a, b) => a.name.localeCompare(b.name));
} }
@ -94,38 +71,38 @@ export const handlers = [
sortBy: sort, sortBy: sort,
}); });
}), }),
http.get('https://api.example.com/transactions', ({ request }) => { http.get("https://api.example.com/transactions", ({ request }) => {
const url = new URL(request.url); const url = new URL(request.url);
// Get query parameters // Get query parameters
const userId = url.searchParams.get('userId'); const userId = url.searchParams.get("userId");
const state = url.searchParams.get('state'); const state = url.searchParams.get("state");
const statusCode = url.searchParams.get('statusCode'); const statusCode = url.searchParams.get("statusCode");
// Filter transactions based on query parameters // Filter transactions based on query parameters
let filteredTransactions = [...transactionDummyData]; let filteredTransactions = [...transactionDummyData];
if (userId) { if (userId) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
tx => tx.user.toString() === userId (tx) => tx.user.toString() === userId
); );
} }
if (state) { if (state) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
tx => tx.state.toLowerCase() === state.toLowerCase() (tx) => tx.state.toLowerCase() === state.toLowerCase()
); );
} }
if (statusCode) { if (statusCode) {
filteredTransactions = filteredTransactions.filter( filteredTransactions = filteredTransactions.filter(
tx => tx.pspStatusCode.toString() === statusCode (tx) => tx.pspStatusCode.toString() === statusCode
); );
} }
return HttpResponse.json({ return HttpResponse.json({
transactions: filteredTransactions, transactions: filteredTransactions,
count: filteredTransactions.length count: filteredTransactions.length,
}); });
}), }),
]; ];

View File

@ -3,8 +3,8 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ /* config options here */
webpack: (config) => { webpack: (config) => {
if (process.env.NEXT_PUBLIC_API_MOCKING === 'enabled') { if (process.env.NEXT_PUBLIC_API_MOCKING === "enabled") {
config.resolve.alias['@mswjs/interceptors'] = false; config.resolve.alias["@mswjs/interceptors"] = false;
} }
return config; return config;
}, },

View File

@ -11,6 +11,8 @@
--text-tertiary: #{$text-tertiary}; --text-tertiary: #{$text-tertiary};
--hover-color: #{$hover-color}; --hover-color: #{$hover-color};
--font-family-base: #{$font-family-base}; --font-family-base: #{$font-family-base};
--button-primary: #{$button-primary};
--button-secondary: #{$button-secondary};
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {

View File

@ -5,6 +5,8 @@ $background-primary: rgb(69, 190, 171);
$text-primary: #000000; $text-primary: #000000;
$text-secondary: #555555; $text-secondary: #555555;
$text-tertiary: #ffffff; $text-tertiary: #ffffff;
$button-primary: #0070f3;
$button-secondary: #ff00ff;
$hover-color: rgba(0, 0, 0, 0.08); $hover-color: rgba(0, 0, 0, 0.08);
$font-family-base: "Roboto", "Helvetica", "Arial", sans-serif; $font-family-base: "Roboto", "Helvetica", "Arial", sans-serif;